Информационная безопасность |
Абстрактный доступ к БД с помощью ADODB
Maxim Matyukhin
Подробное описание абстрактного класса баз данных ADODB. Установка, примеры использования, особенности, ADODB & PEAR.
1. Пару слов об ADODB 2. Установка 3. Простые примеры 4. Практическое использование
4.1 Постраничный вывод и ограничение SELECT-запросов 4.2 Генерирование INSERT/UPDATE запросов 4.3 Работа с транзакциями 4.4 Последовательности 4.5 Кеширование запросов 4.6 Статистика запросов
5. ADODB & PEAR 6. Заключение
1. Пару слов об ADODB
Для начала, скажу что статья рассчитана на программистов, имеющих опыт работы с СУБД, а не на начинающих пхпешников. Я предполагаю, что вы знакомы с PHP, ОПП, SQL и имеете опыт разработки web-приложений.
ADODB - это абстрактный класс доступа к базам данных, написанный на PHP.
Для тех, кто в танке поясню на примере.
Предположим вы написали скрипт под mysql. И тут заказчик говорит Вам, что хостинг меняется и там есть только PostgreSQL. Если вы не использовали класс абстрактного доступа к базам данных, то вам пришлось бы:
- заменить весь код работы с mysql на postgresql
- переписать SQL-запросы (так как есть отличия)
Если бы вы использовали абстрактный слой доступа к БД, то вам скорее всего не пришлось бы менять php-код (только в одном месте указали бы что используете postgresql) и изменить SQL-запросы (хотя иногда и это не понадобилось бы).
Я намеренно в этом описании использовал фразу "абстрактный класс доступа к БД",
поскольку ADODB - не единственный подобный класс. Наиболее известные конкуренты:
Насколько я знаю, другие классы имеют слабую функциональность, хотя должен признать, работают быстрее. Многие пишут такие классы сами, но я не сторонник изобретения велосипедов.
Противники таких массивных классов, как ADODB или Pear::DB утверждают, что их использование плохо сказывается на производительнеости. Да, производительность падает и это вполне логично. НО:
- Скорость не часто является самым важным фактором
(Мало кто из вас пишет сайты с очень большой нагрузкой, на которых бы это снижение производительности стало критичным)
- Использование таких классов повышает производительность программиста
- При использовании софта, типа phpAccelerator падение производительности будет не таким заметным (и я не поверю, что популярные сайты не имеют возможности использовать такой софт)
- Разработчики ADODB написали php-extension, который ускоряет работу класса (но работать можно и без него)
От себя могу добавить, что многие из написанных мною сайтов используют ADODB и проблем с производительностью не имеют.
2. Установка
Здесь все просто. Скачайте с http://php.weblogs.com/adodb архив и распакуйте его (например в папку ./adodb).
Все, класс готов к использованию.
Можете еще скачать и php-extension, но я его использовать не пробовал. Чтобы использовать класс, вам необходимо включить (include) файл ./adodb/adodb.inc.php
3. Простые примеры
Пример 1
// подключаем класс include_once("adodb/adodb.inc.php");
// указываем тип БД $conn = &ADONewConnection('mysql'); // соединяемся с БД $conn->Connect('localhost', 'root', 'password', 'scripts'); // режим отладки - включен (true) $conn->debug = true; $conn->setFetchMode(ADODB_FETCH_ASSOC); ?>
Данный пример демонстрирует подключение к БД. В строке
= &ADONewConnection('mysql'); ?>
Создается объект соединения с базой данных. Именно через поля и методы данного объекта и будет в дальнейшем вестись работа с базой данных.
Что касается режима отладки, то по умолчанию он выключен. При включенном режиме отладки на экран броузера будут выводиться SQL-запросы и тексты ошибок (если такие были). Очень упрощает процесс написания и отладки скриптов.
Метод $conn->setFetchMode() - указывает, каким образом данные о записях будут записаны в массив - будет ли это ассоциативный массив, или простой нумерованный или и тот и другой. Ей нужно установить одно из значений (0, 1, 2, 3). Для пояснений приведу код из исходников adodb:
('ADODB_FETCH_DEFAULT',0); define('ADODB_FETCH_NUM',1); define('ADODB_FETCH_ASSOC',2); define('ADODB_FETCH_BOTH',3); ?>
Судя по исходникам ADODB_FETCH_DEFAULT == ADODB_FETCH_BOTH
Теперь сделаем запрос к БД:
// делаем запрос к БД $res = $conn->Execute("SELECT id, title, description FROM tab"); // если по запросу найдены записи в таблице if ($res && $res->RecordCount() > 0) { // выводим эти записи в цикле while (!$res->EOF) { echo "ID = ".$res->fields['id']."\n"; echo "title = ".$res->fields['title']."\n"; echo "description".$res->fields['description']; // переходим к следующей записи $res->MoveNext(); } } ?>
Вот простейший пример запроса к БД. Метод $conn->Execute() выполняет запрос к базе данных и возвращает множество записей (recordset). Множество записей (recordset) - в ADODB является отдельным объектом, который имеет свои поля и методы для работы с полученными записями. Некоторые из них использованы в данном примере.
- $res->EOF - равен true если обработаны все записи множества
- $res->fields - хранит ассоциативный массив значений текущей записи
- $res->RecordCount() - возвращает количество строк, полученных входе выполнения запроса
- $res->MoveNext() - переходит к следующей записи (в массив $res->fields будет занесена следующая запись множества)
Метод $conn->Execute() - может быть использован для любых запросов:
// делаем вставку строки $conn->Execute("INSERT INTO tab(name, value) VALUES ('name', 'ha ha ha')"); // получаем идентификатор вставки // аналог mysql_insert_id(); $id = $conn->Insert_ID();
$conn->Execute("DELETE FROM tab WHERE id = ".$id); ?>
Опишу еще некоторые полезные методы класса AdoConnection:
- $conn->getRow($sql) - возвратит массив со значениями первой записи из всего множестве найденных записей.
- $conn->getAll($sql) - возвратит 2-мерный массив со всеми найденными записями
4. Практическое использование
Думаю этот раздел будет наиболее интересен программистам.
4.1 Постраничный вывод и ограничение SELECT-запросов
Вообще-то не все базы данных умеют делать запросы типа: SELECT * FROM tab LIMIT 0, 10 а все те, которые умеют, делают это по разному:
- MySQL:
- SELECT * FROM tab LIMIT 0, 10
- PostgreSQL:
- SELECT * FROM tab OFFSET 0, LIMIT 10
- FireBird:
- SELECT FIRST 10 SKIP 0 * FROM tab
Класс adodb сам может делать ограниченные выборки, составляя правильные SQL-запросы под указанную БД, поддерживающую лимитированные SELECT-запросы
= $conn->SelectLimit("SELECT * FROM tab", 10, 0); ?>
Метод $conn->SelectLimit() сам построит правильный SQL-запрос. На основе этого метода в ADODB работают функции для постраничной выборки:
// определяем текущую страницу $start = max(1, intval($_GET['start'])); // количество записей на странице $rows_per_page = 10; $res = $conn->PageExecute("SELECT * FROM tab", $rows_per_page, $start);
// получаем найденное количество записей $records_amount = $res->MaxRecordCount(); ?>
Метод $conn->PageExecute() кроме простого LIMIT-запроса делает автоматически еще и запрос типа: SELECT COUNT(*) FROM tab
Таким образом он сам узнает, сколько всего по данному запросу найдено строк. Это количество можно узнать с помощью метода: $res->MaxRecordCount();
Также для управления постраничным выводом есть следующие методы:
- $res->AbsolutePage() - возвращает текущую страницу
- $res->AtFirstPage() - возвращает true если текущая страница - первая
- $res->AtLastPage() - возвращает true если текущая страница - последняя
- $res->LastPageNo() - возвращает номер последней страницы
4.2 Генерирование INSERT/UPDATE запросов
Для начала пример:
// пример генерировани INSERT-запроса
// массив, который нужно вставить в таблицу $frm = array("field1"=>"value1", "field2"=>"value2"); // делаем пустой запрос $res = $conn->Execute("SELECT * FROM tab WHERE id = -1"); // формируем SQL-запрос $sql = $conn->GetInsertSQL($res, $frm); // выполняем запрос $conn->Execute($sql)
// пример генерирования UPDATE-запроса
// получаем данные о строке, которую нужно обновить $res = $conn->Execute("SELECT * FROM tab WHERE id = 17"); $sql = $conn->GetUpdateSQL($res, $frm); // выполняем запрос $conn->Execute($sql) ?>
Так вот идея в том, чтобы все данные, которые нужно вставить записать в ассоциативный массив. Сделать запрос к БД чтобы получить имена полей таблицы и сконструировать SQL-запрос по этим данным.
Уверен, что будет много противников этого метода (мол лишний SQL-запрос к БД делвть), но мне эти функции кажутся очень удобными.
4.3 Работа с транзакциями
Ну это вообще сказка :). Вот пример из мануала:
$conn->StartTrans(); $conn->Execute("update table1 set val=$val1 where id=$id"); $conn->Execute("update table2 set val=$val2 where id=$id"); $conn->CompleteTrans(); ?>
Метод $conn->CompleteTrans(); сам проверит, были ли ошибки и если так - сделает откат.
ADODB имеет еще и другие функции для работы с транзакциями, но они устарели и разработчики ADODB рекомендуют использовать этот вариант.
4.4 Последовательности
Часто при работе с таблицами каждой записи нужно присвоить уникальный идентификатор, который потом используется в качестве первичного ключа. Но не все СУБД поддерживают такую возможность. ADODB эмулирует эту возможность почти для всех СУБД. На практике это выглядит примерно так:
$uid = $conn->GenID('site_users'); $conn->Execute("INSERT INTO site_users(uid, login, password) VALUES (".$uid.", '$login', '$password')"); ?>
Метод $conn->GenID() создает последовательность site_users (если она до этого не была создана) и возвращает значение на единицу больше чем текущее значение последовательности.
4.5 Кеширование запросов
ADODB поддерживает серверное кеширование запросов. Суть в том, что при первом выполнении запроса его результаты заносятся в кеш-файл. При последующем таком же запросе (если кеш-файл не устарел) данные будут браться из файла.
Честно говоря, мне не нравится метод, которым они производят кеширование (по-моему они слишком уж универсальным сделали его) и предпочитаю делать кеширование своими руками.
Если вас все-таки интересует кеширование, то работает оно так:
$ADODB_CACHE_DIR = '/tmp/ADODB_cache'; $rs = $conn->CacheExecute('SELECT * FROM tab'); ?>
По умолчанию время жизни кеш-файлов - 1 час. Это время можно изменить 2-мя путями:
$conn->cacheSecs = 24*3600 // 24 часа $rs = $conn->CacheExecute('SELECT * FROM tab');
// или так: // время жизни кеша может задаваться первым параметром // метода CacheExecute $rs = $conn->CacheExecute(24*3500, 'SELECT * FROM tab'); ?>
4.6 Статистика запросов.
Наверное видели на некоторых сайтах выводится статистика:
Страница сгенерирована за 0.0016 секунд. Запросов к базе данных - 12
Как вычисляется время генерирования страницы - к данной статье не относится, а вот посчитать количество запросов к БД (а также посчитать количество запросов, взятых из кеша) ADODB позволяет:
// пример взят из мануала function CountExecs($conn, $sql, $inputarray) { global $EXECS; $EXECS++; }
function CountCachedExecs($conn, $secs2cache, $sql, $inputarray) { global $CACHED; $CACHED++; }
$conn = NewADOConnection('mysql'); $conn->Connect(...); $conn->fnExecute = 'CountExecs'; $conn->fnCacheExecute = 'CountCachedExecs';
... // выводим статистику echo "Всего запросов к базе данных: ".$EXECS+$CACHED." "; echo "Из них взято из кеша : ".$CACHED.""; ?>
Данные функции вызываются до запроса, поэтому вы можете с их помощью переписать SQL-запрос.
5. ADODB & PEAR
Я являюсь фанатом как adodb так и репозитария PEAR.
К сожалению основным классом работы с базами данных в PEAR является PEAR::DB
И многие PEAR-классы используют его. Что же делать любителям adodb?
Во-первых, если хорошо присмотреться, то классов, использующих PEAR::DB не так уж и много. У меня почти весь pear-репозитарий на компьютере и там pear::DB используют лишь
- DB::NestedSet
- DB::DataObject
- DB::Pager
- DB::QueryTool
- HTML::Select
- XML::sql2xml
- Auth
- Cache
- Log
- LiveUser
- Mail::Queue
- Translation
Во-вторых, многие классы использую "контейнеры", и для этих классов можно написать контейнер, использующий ADODB (как писать контейнеры - смотрите на примере контейнеров pear::DB указанных классов).
В-третьих, ADODB имеет файл adodb-pear.inc.php который является эмуляцией класса PEAR::DB и остальные классы можно подогнать под работу с adodb с минимальными телодвижениями (часто достаточно в тексте класса строку require_once('DB.php'); заменить на require_once('adodb-pear.inc.php'); но так бывает не всегда).
Так что ADODB можно успешно применять с pear-классами. Приведу пример использования adodb c классом pear::XML::sql2xml. Для тех кто не в курсе - этот класс трансформирует результат запроса (SELECT) к БД в XML-строку:
require_once("adodb/adodb-pear.inc.php"); require_once("XML/sql2xml.php");
$db = DB::connect("mysql://root@localhost/lot"); $sql2xml = new xml_sql2xml(); $result = $db->getAll("select * from lot_sessions"); $xmlstring = $sql2xml->getXML($result); ?>
Те кто уже имеют опыт работы с XML_sql2xml наверное чаще применяют код, который предлагает автор класса XML_sql2xml:
require_once("XML/sql2xml.php"); $sql2xml = new xml_sql2xml("mysql://root@localhost/lot"); $sql2xml->Add("select * from lot_sessions"); $xmlstring = $sql2xml->getXML(); ?>
и
$db = DB::connect("mysql://root@localhost/lot"); $sql2xml = new xml_sql2xml(); $result = $db->query("select * from lot_sessions"); $xmlstring = $sql2xml->getXML($result); ?>
Оба эти примера не сработают и нужно будет править класс XML_sql2xml.
6. Заключение
Поскольку статья носит ознакомительный характер, многое осталось "за кадром". Я не пытался описать все классы, поля и методы - для этого есть официальная документация. Также я не описывал функциональные возможности, которые не использовал на практике:
- хранение сессий в БД (в том числе и зашифрованных сессий)
- работа с хранимыми процедурами
- работа с БД, находящейся на удаленном сервере
- словари (позволяют программно создавать базы данных и таблицы)
Ссылки по теме
|