понедельник, 28 января 2019 г.

Абстрактный доступ к БД с помощью ADODB

Абстрактный доступ к БД с помощью ADODB

Автор: Maxim Matyukhin
Подробное описание абстрактного класса баз данных ADODB. Установка, примеры использования, особенности, ADODB & PEAR.
Установка, примеры использования, особенности, 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
<?php // подключаем класс include_once("adodb/adodb.inc.php");
// указываем тип БД $conn = &ADONewConnection('mysql'); // соединяемся с БД $conn->Connect('localhost''root''password''scripts'); // режим отладки&nbsp;&#151; включен (true) $conn->debug true$conn->setFetchMode(ADODB_FETCH_ASSOC); ?>&lt;/php> 
Данный пример демонстрирует подключение к БД. В строке
<?php $conn = &ADONewConnection('mysql'); ?> 
Создается объект соединения с базой данных. Именно через поля и методы данного объекта и будет в дальнейшем вестись работа с базой данных.
Что касается режима отладки, то по умолчанию он выключен. При включенном режиме отладки на экран броузера будут выводиться SQL-запросы и тексты ошибок (если такие были). Очень упрощает процесс написания и отладки скриптов.
Метод $conn->setFetchMode() — указывает, каким образом данные о записях будут записаны в массив — будет ли это ассоциативный массив, или простой нумерованный или и тот и другой. Ей нужно установить одно из значений (0, 1, 2, 3). Для пояснений приведу код из исходников adodb:
<?php   define('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
Теперь сделаем запрос к БД:
<?php // делаем запрос к БД $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() — может быть использован для любых запросов:
<?php // делаем вставку строки $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:
ВЫБРАТЬ * ИЗ ВСТАВКИ LIMIT 0, 10 
PostgreSQL:
ВЫБРАТЬ * ОТ вкладки OFFSET 0, LIMIT 10 
FireBird:
ВЫБЕРИТЕ ПЕРВЫЙ 10 ПРОПУСТИТЬ 0 * ОТ вкладки
Класс adodb сам может делать ограниченные выборки, составляя правильные SQL-запросы под указанную БД, поддерживающую лимитированные SELECT-запросы
<? $res $conn->SelectLimit("SELECT * FROM tab"100); ?>
Метод $conn->SelectLimit() сам построит правильный SQL-запрос. На основе этого метода в ADODB работают функции для постраничной выборки:
<?php // определяем текущую страницу $start max(1intval($_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 запросов
Для начала пример:
<?php // пример генерировани 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 Работа с транзакциями
Ну это вообще сказка :). Вот пример из мануала:
<?php
   $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 эмулирует эту возможность почти для всех СУБД. На практике это выглядит примерно так:
<?php
   $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 поддерживает серверное кеширование запросов. Суть в том, что при первом выполнении запроса его результаты заносятся в кеш-файл. При последующем таком же запросе (если кеш-файл не устарел) данные будут браться из файла.
Честно говоря, мне не нравится метод, которым они производят кеширование (по-моему они слишком уж универсальным сделали его) и предпочитаю делать кеширование своими руками.
Если вас все-таки интересует кеширование, то работает оно так:
<?php
   $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 позволяет:
<?php // пример взят из мануала 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 "Всего запросов к базе данных: <b>".$EXECS+$CACHED."</b><br />";
echo 
"Из них взято из кеша : <b>".$CACHED."</b>"?>
Данные функции вызываются до запроса, поэтому вы можете с их помощью переписать SQL-запрос.
5. ADODB & PEAR
Я являюсь фанатом как adodb так и репозитария ГРУША.
К сожалению основным классом работы с базами данных в PEAR является PEAR::DB
И многие PEAR-классы используют его. Что же делать любителям adodb?
Во-первых, если хорошо присмотреться, то классов, использующих PEAR::DB не так уж и много. У меня почти весь pear-репозитарий на компьютере и там pear::DB используют лишь
  • DB :: NestedSet
  • DB :: DataObject
  • DB :: Пейджер
  • DB :: QueryTool
  • HTML :: Выбрать
  • XML :: SQL2XML
  • Auth
  • кэш
  • Журнал
  • LiveUser
  • Mail :: Tail
  • Перевод
Во-вторых, многие классы использую "контейнеры", и для этих классов можно написать контейнер, использующий 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-строку:
<?php 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:
<?php require_once("XML/sql2xml.php"); $sql2xml = new xml_sql2xml("mysql://root@localhost/lot"); $sql2xml->Add("select * from lot_sessions");                         $xmlstring $sql2xml->getXML(); ?>
и
<?php
$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. Заключение
Поскольку статья носит ознакомительный характер, многое осталось "за кадром". Я не пытался описать все классы, поля и методы — для этого есть официальная документация. Также я не описывал функциональные возможности, которые не использовал на практике:
  • хранение сессий в БД (в том числе и зашифрованных сессий)
  • работа с хранимыми процедурами
  • работа с БД, находящейся на удаленном сервере
  • словари (позволяют программно создавать базы данных и таблицы)
Ссылки по теме