Абстрактный доступ к БД с помощью 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 - не единственный подобный класс. Наиболее известные конкуренты:

  • Pear::DB
  • Pear::MDB

Насколько я знаю, другие классы имеют слабую функциональность, хотя должен признать, работают быстрее. Многие пишут такие классы сами, но я не сторонник изобретения велосипедов.

Противники таких массивных классов, как 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. Заключение

Поскольку статья носит ознакомительный характер, многое осталось "за кадром". Я не пытался описать все классы, поля и методы - для этого есть официальная документация. Также я не описывал функциональные возможности, которые не использовал на практике:

  • хранение сессий в БД (в том числе и зашифрованных сессий)
  • работа с хранимыми процедурами
  • работа с БД, находящейся на удаленном сервере
  • словари (позволяют программно создавать базы данных и таблицы)

Ссылки по теме

 


Страница сайта http://silicontaiga.ru
Оригинал находится по адресу http://silicontaiga.ru/home.asp?artId=5943