среда, 30 января 2019 г.

Абстрактные классы БД

Абстрактные классы БД

Автор: Дмитрий Лебедев
Классы, позволяющие работать с разными базами данных через один стандартный набор функций. Полезны при смене базы данных и работе с несколькими БД одновременно.
Тема эта регулярно всплывает в форуме. Собственно, зачем они нужны, эти абстрактные классы баз данных? Большинство сайтов по-хорошему обойдутся и без таких средств унификации. Они работают либо без базы, либо на MySQL, чего для большей части проектов вполне достаточно. Да, да! Достаточно! Когда нагрузки возрастут, вот тогда купят и поставят Оракл, а чтобы скрипты не переписывать, пусть пользуются абстрактными классами. :) Про бесплатные и фирменные базы данных разговор отдельный, грозит превратиться в пустой флейм (а конференциях именно этим все и заканчивается), поэтому не буду его продолжать.
Абстрактные классы позволяют разработчикам писать код без оглядок на то, какая база данных стоит — только на ее возможности и особенности работы. Очень полезно, если работа идет с несколькими базами одновременно (не надо вспоминать, например, что писать: mysql_fetch_array или OCIFetchInto). Смена базы данных тоже многократно облегчится — надо будет всего лишь изменить тип базы в инициализации объекта. Разумеется, все эти красивые вещи оборачиваются большей ресурсоемкостью программ.
Сразу отмечу, что, первое, эти классы я нашел не в каких-либо дебрях сети, а прямо "на поверхности" — hotscripts.com. И второе — написать свой собственный класс — не проблема. И, конечно же, всех классов не опишешь, поэтому если вы считаете, что я пропустил здесь что-то полезное и хорошее, пишите.
Немного о схеме работы с абстрактными классами.
Нормальная схема — это два класса: соединение с базой и результат запроса. В принципе, идентификатор результата запроса можно было держать и внутри объекта БД (например в массиве результатов), но порядка и удобства ради результаты вынесены в отдельный класс.
Итак, найдено пять классов. Описываю в порядке убывания удобства (удобства на мой взгляд).
ADOdb (по имеющимся сведениям — переписанный в php из asp)
Самый расписанный, самый документированный. Есть примеры использования. А оно достаточно простое. Настраиваете в файле adodb.inc.php параметры базы данных, которые будут использоваться по умолчанию. А можно и не настраивать.
Пример из документации:
включают в себя ( "adodb.inc.php");
ADOLoadCode ( 'MySQL');
$ db = NewADOConnection ();
$ db-> Connect ("localhost", "root", "password", "mydb");
$ result = $ db-> Execute ("SELECT * FROM сотрудников");
if ($ result === false) die ("fail");
while (! $ result-> EOF) {
  для ($ i = 0, $ max = $ result-> FieldCount (); $ i <$ max; $ i ++)
    выведите $ result-> fields [$ i]. ' «;
  $ Result-> MoveNext ();
  печать "<br>
«;
  }
Если нужна база не MySQL, а Oracle, меняем вторую строку на
ADOLoadCode ( 'оракула');
Программа сама подключает функцией include_once файл с классом для нужной базы данных.
Поддержка разных баз осуществляется через дочерние классы.
В общем, все работает и пишется хорошо, и требования тоже "хорошие": объем подключаемого кода для БД MySQL равняется 34 килобайтам кода. Для справки: код для этого сайта включая класс шаблона и сами шаблоны "весит" 66 килобайт.
DAC (Класс доступа к базе данных).
В использовании — примерно то же, что и AdoDB. Только в "комплекте" нет руководства. :) Разные базы поддерживаются дочерними классами, которые, насколько я понял, подключаются автоматически. Но здесь функций для выполнения запроса несколько — select, insert, update и execute. Не знаю, может, это и удобнее с точки зрения обработки результата, но писать программы по моим прикидкам не будет ни удобнее, ни сложнее (по крайней мере, мне не приходилось сталкиваться с ситуацией, когда тип запроса выбирался в зависимости от ситуации).
class.DBI (Database independent class, говорят, что переписан с Perl-овой версии).
Описано хорошо, но на сайте. Функции разделены, но на две (выборка и остальные запросы). Чтобы достать из архива какие-либо файлы, надо проявить хорошую смекалку. Потому что архив не распаковывается — там в именах файлов двоеточия, и система грязно ругается. Заходим в файл class.DBI-....tgz, копируем оттуда в нужную директорию файл class.DBI-0.3.8.tar и открываем его в UltraEdit. Народным методом copy/paste достаем фрагмент кода под нужную базу. Фуф!
В отличие от предыдущих классов здесь запрос отправляется в базу конструктором класса запроса. В одном из классов нашел ошибку — вместо str_replace используется ereg_replace.
DIOW (Оболочка независимого от базы данных объекта).
А вот этим классом настоятельно не советую пользоваться. Разделение на разные базы сделано самым глупым способом — если переменная с типом базы, и такая конструкция:
if ($ database_type == 1) {
  ...
  }
elseif ($ database_type == 2) {
  ...
  };
А в ней — самое страшное. Попробуйте найти это сами.
if ($ database_type == 2) {
  $ this-> my_temp_resultID = mysql_query ($ someSQL, $ this-> my_connection);
  $ this-> my_temp_result_object-> numrows = mysql_num_rows ($ this-> my_temp_resultID);
  $ this-> my_temp_result_object-> numcols = mysql_num_fields ($ this-> my_temp_resultID);

  // заполняем столбцы из результирующего набора
  для ($ j = 0; $ j <$ this-> my_temp_result_object-> numcols; $ j ++) {
    $ this-> my_temp_result_object-> column_name [$ j] = 
 mysql_fieldname ($ this-> my_temp_resultID, $ j);
    };

  // заполняем элементы данных из набора результатов
  для ($ i = 0; $ i <$ this-> my_temp_result_object-> numrows; $ i ++) {
    $ x = mysql_fetch_row ($ this-> my_temp_resultID);
    для ($ j = 0; $ j <$ this-> my_temp_result_object-> numcols; $ j ++) {
      $ this-> my_temp_result_object-> element [$ i] [$ j] = $ x [$ j];
      };
    };

  return $ this-> my_temp_result_object;
  mysql_free_result ($ this-> my_temp_resultID);
  };
Перед return есть два вложенных цикла, которые тупо берут данные из базы и скидывают их в массив. Конечно же, потом с этим массивом делать можно что угодно, "хоть веревки вейте, хоть ездовую собаку делайте", но это стоит дополнительной занимаемой памяти.
Конечно же, иногда требуется произвести операцию, которая не по силам базе данных, но это один случай из ста. В остальных же нужно просто получать данные и выводить их в документ. При этом сразу после вывода массив строки заменяется на новый, почти такой же. Здесь же этого не происходит.
Тут, конечно, мне можно возразить, мол, сам шаблонами пользуешься, и до конца держишь все в переменной (финал моих скриптов такой: $root->ugh(); — это выводится готовый документ :). Ну, шаблоны — это совсем другое дело. А если я прикручу этот класс к своим шаблонам или к другому скрипту, в котором держать все данные в массиве не нужно, памяти будет требоваться еще больше.
Если же кому-то нужно именно скидывать в один массив все результаты запроса, пользуйтесь другими классами — всего-то четыре строки своего кода.
К тому же, как вы понимаете, освобождения результата в конце функции не произойдет, потому что команда на завершение работы функции — return — уже дана. Если скрипт, который вызывает несколько "хороших" запросов, одновременно вызовет много пользователей, ой, как плохо станет серверу!