| 
| Информационная безопасность |  Абстрактный доступ к БД с помощью 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. ЗаключениеПоскольку статья носит ознакомительный характер, многое осталось "за кадром". Я не пытался описать все классы, поля и методы - для этого есть официальная документация. Также я не описывал функциональные возможности, которые не использовал на практике: 
хранение сессий в БД (в том числе и зашифрованных сессий) работа с хранимыми процедурами работа с БД, находящейся на удаленном сервере словари (позволяют программно создавать базы данных и таблицы) 
 Ссылки по теме
 |