![]() |
Класс базируется на основе библиотеки Максима Полторака phpDBTree 1.4 (http://dev.e-taller.net/dbtree/).
ВНИМАНИЕ! В текущую версию внесены значительные изменения, которые не затрагивают интерфейс библиотеки (то есть вам не придется переписывать свои скрипты), но изменяют требования. В частности, из обязательных требований убраны требования наличия GetText и ADODB.
Внимательно ознакомьтесь со внесенными изменениями (смотрите их описание в разделе "Загрузить").
Основной особенностью библиотеки является, то, что все запросы в методах переписаны согласно стандартам ANSI и работают без изменений на подавляющем большинстве баз данных.
Поддержка транзакций на основе класса ADODB. Если вы не хотите использовать ADODB, но если хотите поддерживать механизм транзакций на основе используемого вами драйвера базы данных, реализуйте их самостоятельно, переписав соответствующие функции в прилагаемом драйвере-примере для базы MySql.
Библиотека поддерживает кэширование SQL запросов на на основе класса ADODB, но если хотите поддержать кэширование на основе используемого вами драйвера базы данных, реализуйте его самостоятельно, переписав соответствующие функции в прилагаемом драйвере-примере для базы MySql.
Библиотека работает с поддержкой технологии GetText. Эта возможность опциональна. Класс сам определит подключено ли соответствующее расширение. Если GetText отсутствует, класс будет выводить сообщения средствами PHP.
Синтаксис использования библиотеки очень прост:
<?php
|
После чего, переменная $dbtree содержит объект для работы с деревьями.
Давайте рассмотрим параметры конструктора: $table – содержит название таблицы, в которой хранится интересующее нас дерево; $prefix - содержит уникальный префикс для всех полей таблицы с данными, например, если ваше поле называется user_id, то уникальный префикс будет user; $db – это объект ADODB или объект прилагаемого к классу демонстрационного драйвера, или объект написанного вами драйвера.
В качестве практического примера я рассмотрю возможность хранения в рамках одной таблицы нескольких деревьев. Также мы предположим, что структура нашего сайта хранится в виде дерева Nested Sets. Мы посмотрим, какие возможности предоставляет нам библиотека для работы с сайтом и для его обслуживания.
И так, приступим.
Исходные данные: мы будем использовать дерево для хранения структуры сайта (то есть перечень его разделов). Чтобы усложнить задачу, предположим, что сайт у нас поддерживает 2 языка. Это значит, что структур сайта у нас будет 2. Чтобы не множить таблицы, мы оба дерева (русское и английское) будем хранить в одной таблице.
В этом конкретном примере мы с вами посмотрим, какие возможности библиотека предоставляет создателю сайта, и каким образом вы можете облегчить свою жизнь.
Table sections ========== CREATE TABLE `sections` ( `section_id` bigint(20) NOT NULL default '0', `section_left` bigint(20) NOT NULL default '0', `section_right` bigint(20) NOT NULL default '0', `section_level` int(11) 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`), UNIQUE KEY `section_id` (`section_id`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8 |
Первые 4 поля имеют отношения непосредственно к структуре дерева и напрямую в них вмешиваться не следует. section_lang служит семафором, указывая на принадлежность элемента к определенному дереву. section_name – имя раздела для вывода его пользователю. section_path – минимальный элемент пути к разделу, например, sample. section_full_path будет содержать полный путь по сайту до указанного элемента, например, my_site/sample/.
Я приведу свои настройки для примера. В дальнейшем буду считать, что вы эти действия уже выполнили и буду их опускать, считая, что объект драйвера бызы данных будет у вас в переменной $db.
<?php
|
<?php
|
Далее, нам необходимо заполнить таблицу первоначальными инициализационными данными.
<?php
|
Теперь наша таблица содержит все первоначальные данные и мы можем начинать работу.
Какие этапы нам нужно пройти?
Во-первых, получить все имеющееся дерево для нужной нам языковой версии сайта.
Во-вторых, вывести его на экран пользователю, чтобы он выбрал, у какого раздела сайта мы создаем подраздел.
В-третьих, получить данные о новом разделе от пользователя.
В-четвертых, создать новый раздел в структуре дерева сайта.
азумеется, есть еще подэтапы проверки введенных пользователем данных, но мы их опустим, как опустим 3 этап, потому что он не имеет отношения к решаемой проблеме. Мы могли бы опустить и 2 этап, но так как нам он часто будет нужен, я приведу его 1 раз и далее буду ссылаться на этот пример.
<?php
|
Третий этап мы пропускаем, потому что принятие данных от пользователя или из другого источника не является предметом материала, это вы и сами прекрасно реализуете.
А мы переходим к 4 этапу. Данные получены, проверены и нам надо на их основе добавить новый раздел. Давайте перечислим, какие данные нам нужны, чтобы добавить новый раздел: section_id – номер раздела К КОТО ОМУ добавляем новый подраздел, section_name – имя раздела, section_lang – идентификатор языковой версии дерева, section_path – отрезок пути для данного конкретного раздела.
При добавлении нового раздела нам необходимо рассчитать section_full_path. Для этого нам необходимо получить всех родителей добавляемого раздела и сложить их section_path в единый путь.
азместим все эти данные для удобства в массиве $section: $section['section_id'], и так далее.
<?php
|
К этому моменту, если не произошло ошибок, у нас в русском языковом дереве появился новый раздел.
Давайте заодно рассмотрим удаление раздела.
<?php
|
Перемещение раздела к другому родителю с данным классом также не представляет особой проблемы.
Какие этапы нам необходимо пройти?
Во-первых, выбрать узел (также будут перемещаться и все его дети), который хотим переместить.
Во-вторых, требуется выбрать нового родителя (родитель не может находиться в пределах ветки, выбранной для перемещения).
В-третьих, на основании этих данных переместить ветку к новому родителю.
Давайте посмотрим как это делается на практике. Первые два этапа мы опустим, поскольку они сводятся к получению всего дерева и выводу его пользователю для определения нужных section_id разделов. Замечу, что перемещать узлы можно только в пределах одного и того же дерева, то есть, если вы выбрали русскую версию дерева, то и новый родитель должен принадлежать этому же дереву.
Когда section_id перемещаемого узла и section_id нового родителя получены, действуем следующим образом:
<?php
|
Все, ваш раздел успешно перенесен. Не забывайте обрабатывать section_full_path, задавая им новые значения с учетом изменившегося пути.
Таким же образом легко поменять позицию у двух узлов (при этом дети не перемещаются).
<?php
|
Таким образом мы поменяем местами узлы с номерами $id1 и $id2 соответственно.
В общем случае, у нас есть URL, который показывает нам путь по которому шел пользователь (скрипту совершенно не важно – реальный он или виртуальный). На основе URL мы попробуем вычислить номер текущего раздела.
И так, у нас есть URL следующего вида:
http://mysite.ru/en/food/fruits/apples/2005-09-30/ |
Где http://mysite.ru/ - это название вашего сайта, en/ – это код языка, с которым в данный момент работает пользователь, food/fruits/apples/ - это собственно путь раздела, а 2005-09-30/ - это дополнительный параметр для скрипта, не имеющий отношения к пути следования пользователя.
Наша задача по пути раздела получить его section_id, а параметр 2005-09-30 отдать на обработку соответствующего скрипта.
<?php
|
В результате работы указанного выше кода вы получаете: $current_id – номер раздела, в котором сейчас находится пользователь, $page_params содержит массив с дополнительными параметрами, переданными через URL (в нашем случае - 2005-09-30).
Отмечу, что этот механизм позволяет игнорировать в URL ошибки пользователя. Это значит, что если пользователь ошибется в любом элементе пути, ему будет показана страница, которая следовала до этого ошибочного этапа. И только если URL вообще не будет подобран, будет выдана ошибка 404404.
Современный сайт не мыслим без таких удобств, как навигатор (когда пользователь видит дорожку следования по сайту) или подробного меню, предлагающего ему все разнообразие возможных действий. В этой части нашего курса мы с вами посмотрим, какие возможности для реализации подобного предлагает моя библиотека.
Давайте начнем с простого – построим навигатор по сайту. Чно нам для этого понадобится? Да ничего особенного, все данные к этому моменту у нас уже должны быть. Нам нужен только номер раздела, в котором сейчас находится пользователь. По идее, вы должны были его получить, когда определяли, какую страницу показывать пользователю на основании section_full_path.
аз все данные у нас уже есть, остается только построить навигатор и вывести его на эран в нужном месте.
<?php
|
Перейдем к построению меню, для подсказки пользователю возможных действий. Библиотека предлагает вам минимум 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
|
На основе section_level вы легко можете отобразить ветку в виде лесенки (смотрите пример построения всего дерева).
Третий вариант – это показать пользователю только ту часть дерева, которая относится к разделу, в котором он сейчас находится. В нашем случае (смотрите примеры деревьев выше), отот вариант покажет пользователю следующее:
Section Section Section |
Давайте посмотрим, каким образом построить такое дерево. Исходные данные – теже, что и в предыдущих примерах.
<?php
|
Выводим на экран лесенкой, как описано в предыдущих примерах.
Все предыдущие примеры в своей работе не использовали кэширование запросов. Настало время посмотреть, как его использовать и для чего это нужно.
Как правило, дерево сайта меняется не так уж и часто, а потому у нас есть реальная возможность снять нагрузку с сервера, производя кэширование запросов к базе данных. ADODB предлагает, а мой класс поддерживает, кэширование запросов к базе. Если ADODB вам по каким-то пречинам не подходит, вы вполне можете реализовать кэширование сами. Для этого вам необходимо переписать соответствующие функции в предлагаемом с библиотекой драйвере базы данных.
азумеется, вам не стоит использовать кэширование в административном интерфейсе вашего сайта, чтобы видеть все изменения в реальном времени, но для показа сайта пользователю кэширование оправдано и полезно!
Для глобального включения кэширования необходимо константе DB_CACHE задать значение TRUE.
При вызове функций класса, которые поддерживают кэширование (смотрите ниже полное описание всех функций библиотеки), вам необходимо последним параметром передавать время жизни кэша в микросекундах. Например, 3600, составляет 1 час. В течение указанного срока, ADODB будет обращаться к базе данных, только если данный запрос проходит впервые. Если запрос уже был сделан раньше – ADODB возьмет его результаты из кэша, что значительно ускоряет работу вашего сайта и снимает нагрузку с сервера базы данных.
Константа. Если установлена в TRUE, то все функции смогут кэшировать запросы к базе данных средствами ADODB. Подробнее об этой возможности смотрите в описании конкретных функций.
Массив содержит сообщения об ошибках для вывода их пользователям.
Формат массива: array('string (информация об ошибке)').
Массив содержит полную информацию об ошибке для Log файла и программиста.
Формат: array('тип ошибки (1 - фатальная (пишем log), 2 - фатальная (пишем log, посылаем email)', 'описание ошибки string', 'класс, функция, и строка где возникла ошибка', 'доп. инфо. 1', 'доп. инфо. 2').
Служит для прохода по полученным данным. Возвращает ассоциированный массив с данными обо всех запрашиваемых элементах текущей записи.
Возвращает общее число элементов полученных при помощи вызванной ранее функции.
Эта функция не предназначена для прямого вызова и вызывается другими методами класса. В результате ее работы будет возвращена строка условия для запроса.
Вспомогательная внутренняя (private) функция, которая готовит для разных методов класса WHERE часть запроса.
$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).
Очищает текущее дерево и инициализирует его. $data – содержит массив (название поля -> значение) с информацией для дополнительных полей таблицы, которые не являются частью Nested Set данных.
Возвращает информацию о текущем узлом с номером $ID. Параметр $cache указывает, время кэширования (в микросекундах) текущего запроса. 0 или FALSE – не кэшировать. Глобально кэшированием управляет константа DB_CACHE.
Возвращает параметры left, right, level.
Возвращает информацию о родителе текущего узла с номером $ID. Параметр $cache указывает, время кэширования (в микросекундах) текущего запроса. 0 или FALSE – не кэшировать. Глобально кэшированием управляет константа DB_CACHE.
Возвращает всю информацию о родителе в порядке следования полей в таблице.
Вставляет в дерево новый узел дочерний по отношению к элементу с номером $ID. $condition – дополнительные условия для запроса (описание в описании функции _PrepareCondition). $data – содержит массив (поле таблицы -> значение) с информацией для дополнительных полей таблицы, которые не являются частью Nested Set данных.
Возвращает уникальный номер вставленного элемента или FALSE.
Вставляет в дерево новый узел после элемента с номером $ID. $condition – дополнительные условия для запроса (описание в описании функции _PrepareCondition). $data – содержит массив (поле таблицы -> значение) с информацией для дополнительных полей таблицы, которые не являются частью Nested Set данных.
Возвращает уникальный номер вставленного элемента или FALSE.
Перемещает узел с номером $ID и всех его детей к новому разделу с номером $newParentId. Не допускается перемещение узлов в рамках одного родителя. $condition – дополнительные условия для запроса (описание в описании функции _PrepareCondition).
Возвращает TRUE – удача - или FALSE – не удача.
Меняет элементы местами. $id1 и $id2 уникальные номера первого и второго элемента соответственно.
Возвращает TRUE – удача - или FALSE – не удача.
Меняет порядок детей у одного родителя в рамках одного уровня. Все дети переносимого элемента также перемещаются вместе с ним, сохраняя иерархию. $id1 - уникальный номер перемещаемого узла (все его дети будут перемещены вместе с ним), $id2 уникальный номер элемента, относительно которого будет происходить перемещение. $position - позиция, в которую будет помещен переносимый элемент ($id1) относительно другого элемента ($id2): 'after' - переносимый элемент ($id1) будет поставлен после указанного элемента ($id2), 'before' - переносимый элемент ($id1) будет поставлен перед указанным ($id2).
Возвращает TRUE – удача - или FALSE – не удача.
Удаляет узел с уникальным номером $ID. Все его дети перемещаются на уровень выше. $condition – дополнительные условия для запроса (описание в описании функции _PrepareCondition).
Возвращает TRUE – удача - или FALSE – не удача.
Удаляет узел с уникальным номером $ID и всех его детей. $condition – дополнительные условия для запроса (описание в описании функции _PrepareCondition).
Возвращает TRUE – удача - или FALSE – не удача.
Возвращает дерево целиком сортированное по left. $fields – массив с перечислением требуемых вам полей таблицы (array('field1', 'field2'...). $condition – дополнительные условия для запроса (описание в описании функции _PrepareCondition). Параметр $cache указывает, время кэширования (в микросекундах) текущего запроса. 0 или FALSE – не кэшировать. Глобально кэшированием управляет константа DB_CACHE.
Возвращает TRUE – удача - или FALSE – не удача.
Для получения данных дерева вам необходимо воспользоваться методом NextRow().
Выводит всю ветку узла с номером $ID. $fields – массив с перечислением требуемых вам полей таблицы (array('field1', 'field2'...). $condition – дополнительные условия для запроса (описание в описании функции _PrepareCondition). Параметр $cache указывает, время кэширования (в микросекундах) текущего запроса. 0 или FALSE – не кэшировать. Глобально кэшированием управляет константа DB_CACHE.
Возвращает TRUE – удача - или FALSE – не удача.
Для получения данных дерева вам необходимо воспользоваться методом NextRow().
Выводит всех родителей элемента с номером $ID. $fields – массив с перечислением требуемых вам полей таблицы (array('field1', 'field2'...). $condition – дополнительные условия для запроса (описание в описании функции _PrepareCondition). Параметр $cache указывает, время кэширования (в микросекундах) текущего запроса. 0 или FALSE – не кэшировать. Глобально кэшированием управляет константа DB_CACHE.
Возвращает TRUE – удача - или FALSE – не удача.
Для получения данных дерева вам необходимо воспользоваться методом NextRow().
Выводит приоткрытое дерево, где условием приоткрытости выступает элемент с номером $ID. $fields – массив с перечислением требуемых вам полей таблицы (array('field1', 'field2'...). $condition – дополнительные условия для запроса (описание в описании функции _PrepareCondition). Параметр $cache указывает, время кэширования (в микросекундах) текущего запроса. 0 или FALSE – не кэшировать. Глобально кэшированием управляет константа DB_CACHE.
Возвращает TRUE – удача - или FALSE – не удача.
Для получения данных дерева вам необходимо воспользоваться методом NextRow().
Драйвер-пример полностью рабочий и полностью обслуживает новую версию библиотеки, однако я предоставляю его исключительно в качестве примера и НИКАКИХ ГА АНТИЙ, СВЯЗАННЫХ С ЕГО ИСПОЛЬЗОВАНИЕМ НЕ ДАЮ. Настоятельно рекомендую просмотреть все функции данного класса и переписать их под ваши нужды.
Этот класс-драйвер содержит в себе все основные функции, которые необходимы библиотеке. екомендую внимательно ознакомиться со всеми функциями класса и изменить их для ваших нужд.
Если вы работаете с библиотекой PEAR::DB, то вам необходимо переписать данный класс, поместив внутрь его функций вызовы соответствующих функций библиотеки PEAR::DB.
Если вы проявите творческое начало и напишите свой вариант класса для работы с PEAR::DB или любым другим классом для работы с базами данных, а также поделитесь им со мною и общественностью, то я обязательно сообщу об этом в документации к классу, а также объявлю личную благодарность!
Переменная предназначена для хранения соединения с базой данных.
Входные параметры: $host - хост для соединения с базой данных, $user - логин пользователя для соединения с базой данных, $password - пароль пользователя, $database - название базы данных, с которой работаем.
Конструктор создает подключение с базой данных и выбор нужной базы для работы. В случае неудачи одной из операций возвратит false.
Возвращает строку - полное описание последней произошедшей ошибки при вызове любой функции класса.
Закрывает соединение с базой данных.
Исполняет запрос. $sql - строка - правильный SQL запрос к базе данных.
Возвращает объект Recordset или false в случае неудачи.
Генерирует уникальный номер для вставки записи в таблицу. $seqname - название таблицы, где хранится счетчик, $start - если счетчика нет, то указывает его начальную позицию, иначе игнорируется.
Возвращает целое цисло - уникальный порядковый номер для вставки в таблицу.
Генерирует строку INSERT запроса. $recordset - объект, полученный в результате следующего запроса: 'SELECT FROM table WHERE id = -1'. Где table - название таблицы, куда собираетесь вставлять новую запись. $data - массив вставляемых элементов: array('название поля' => 'значение', ...).
Возвращает готовый запрос или пустую строку.
Генерирует строку UPDATE запроса. $recordset - объект, полученный в результате следующего запроса: 'SELECT FROM table WHERE id = xxx'. Где table - название таблицы, где собираетесь обновлять запись, а ххх - номер обновляемой записи. $data - массив обновляемых элементов: array('название поля' => 'значение', ...).
Возвращает готовый запрос или пустую строку.
Возвращает первый полученый параметр из выполненного SQL запроса. $sql - строка с правильным запросом.
Исполняет запрос с организацией кэширования. На данный момент не реализована и полность эквивалетна Execute(). Вы можете самостоятельно реализовать возможность кэширования переписав данную функцию по своему усмотрению.
$cache - время кэширования запроса в микросекундах, $sql - строка запроса.
Функции поддержки транзакций. На данный момент не реализованы. Вы можете самостоятельно реализовать поддержку транзакций переписав данные функции по своему усмотрению.
Класс предназначен для формирования объекта с результатами запроса (после команды Execute() класса DB).
Вы можете расширить набор функций для обслуживания полученного результата. Здесь приведены только функции, необходимые классу.
Предназначена для хранения ресурса с результатами запроса.
предназначена для хранения строки - полного SQL запроса в результате которого возник данный объект.
Принимает $recordset - ресурс с результатами запроса. $sql - текстовая строка - SQL запрос, в результате которого возник данный объект.
Возвращает количество строк, выбранных из базы в результате запроса, породившего данный объект.
Возвращает ассоциированный массив занчаений результата запроса, на котором стоит указатель.