« Доступ к базам данных из php. Часть 4 | Доступ к базам данных из php. Часть 6 » |
Доступ к базам данных из php. Часть 5
Сегодня мы продолжим знакомство с передовыми методиками доступа к базам данных. На очереди рассмотрение паттернов Active Record и Row Data Gateway. Также я расскажу о новой библиотеке adodb.Чем отличается паттерны Active Record и Row Data Gateway от рассмотренного в прошлых статьях Table Data Gateway (шлюза таблицы данных). Шлюз таблицы говорил нам: “давайте, отделим и обособим sql-код обращающийся к БД от остальных частей программы, создадим библиотеку, в которой будет несколько функций, для того чтобы искать сведения в таблицах, функции для добавления, удаления и изменения данных, а затем будем везде в программе использовать только эти функции”. Паттерн Row Data Gateway предлагает каждой записи возвращаемой из выборки поставить в соответствие некоторый объект. Свойствами данного класса будут поля таблицы, методы класса будут служить для сохранения информации в объекте как новой записи, обновления существующей или удаления из таблицы БД той записи, представителем которой является данный объект. Интересной особенностью Row Data Gateway является то, что его объект может представлять собой не запись таблицы, а запись более сложной выборки (например, запрос из нескольких таблиц, содержащий статистические данные, вычисляемые поля …). Конечно, в таком случае код по обновлению информации становится достаточно сложным, но растут и возможности по планированию удобного интерфейса пользователя. Паттерн Active Record очень похож на Row Data Gateway, так, что их часто путают. Основное отличие в том, что объект, привязанный к записи таблицы в случае Row Data Gateway, содержит методы только для чтения информации из базы, сохранения и удаления. Но не содержит методов специфических для предметной модели, которую моделирует наша БД. Например, у вас есть перечень товаров на складе, каждый товар характеризуется количеством. Объекту-записи в случае Row Data Gateway нет никакого дела до того чему равно это поле “количество”. А объект Active Record должен знать и учитывать, что поле “количество” не может быть отрицательным и не может быть больше, например, ста тысяч, поскольку на складе просто нет больше свободного места. Вопрос о том, где хранить логику приложения на клиенте (в виде специальных методов вроде “проверитьКоличество”) или же перенести логику на сервер не имеет однозначного ответа. Я стараюсь, где только можно создавать хранимые процедуры (процедуры работы или проверки данных размещенные не внутри программы, а на сервере СУБД). Это дает плюсы в скорости работы, легкости обновления и внесения исправлений в одном месте – сервере, а не в тысяче разрозненных мест – программ-клиентов. С другой стороны, может быть, вы пишите небольшое приложение, работающее с СУБД, которая просто не поддерживает хранимые процедуры. Или же языковые средства написания хранимых процедур слишком примитивны, получающийся код проверки громоздок и неудобочитаем. Поэтому я с большей радостью приветствую сложившуюся тенденцию внедрять в ядро современных СУБД возможность писать код для хранимых процедур, триггеров не на косноязычном “tsql” или “pl/sql”, а на универсальных языках (java, c#, php). При условии, конечно, что потери скорости не слишком велики. Перенос логики на сторону клиента может быть и вынужденным в случаях, когда этап вноса изменений в СУБД сложен или отложен по времени, например, связь выполняется по низкоскоростным линиям, или изменения накапливаются в течении нескольких часов и только затем (пакетом) сохраняются на сервере БД – в этих случаях следует проверять вводимые данные на предмет корректности как можно скорее и до фактической отправки их на сервер.
Всем хороша рассмотренная в прошлый раз библиотека dbSimple. Удобно писать код отбора данных, проще защищать свое приложение от sql-injection, приятно работать с функциями отбора данных из БД, возвращающими привычные для php массивы. Вот только остается открытым вопрос о том, как быть, если нам нужны не только выборки данных, но и их модификация: нашли запись в таблице товаров по, например, цене, внесли изменения в поля записи и сохранили ее назад в БД. Для того чтобы изменить запись используется команда update:
UPDATE имя_таблицы SET поле = новое_значение WHERE условие_отбора
В большинстве популярных статей рассказ начинается со слов, что adodb – абстрактный класс для доступа к БД. А в качестве “затравки” приводят рассказ: “представьте, что вы сделали сайт под mysql, а на хостинге вдруг оказался postgres и только благодаря adodb вы не переписали с нуля весь код вашего сайта”. Свое негативное отношение к подобному “вдруг” и “выравниванию sql-кода” я высказал еще в первой статье серии. С другой стороны подобная абстракция от того, с какой СУБД мы работаем, необходима для ряда больших и сложных продуктов, например, “коробочных” систем управления сайтами. Если вы вложили силы и деньги во что-то большое, то, очевидно, вы хотите продать его наибольшему количеству клиентов, и сосредотачиваться на одном mysql не рационально. У вас теплится мысль продать этот продукт и для хостингов, где используется postgres и там, где firebird, oracle, mssql. Чем больше баз, под которыми ваш код будет работать, тем лучше. С другой стороны, вы не хотите нести дополнительные затраты по ведению нескольких различающихся версий программы для каждой из СУБД. В качестве первейшего возражения против adodb или подобной ему Pear:DB, приводят фактор снижения производительности. Согласен (каждый слой абстракции добавляет лишние траты памяти и ресурсов процессора), но, с другой стороны, ведь не все мы пишем сайты работающие под высокими нагрузками. А если пишите, то вы будете вынуждены использовать специальные “фишки” вашей СУБД, чтобы выиграть еще пару процентов скорости. Если использование adodb позволит увеличить вашу производительность и сделать за месяц не один, а два сайта – используйте adodb смело. А производительность? А кэширование вам на что? А зачем существуют продукты вроде zend optimizer или phpaccelerator? Кроме того, adodb можно найти практически в любом известном php framework-е или cms. Также adodb может работать под php4 и под php5. Так что если вы еще ни разу не сталкивались с adodb, то самое время с ним познакомиться. Начните с того, что скачайте с домашнего сайта проекта http://adodb.sourceforge.net/ архив с библиотекой и распакуйте его у себя на веб-сервере. Неплохо обзавестись и документацией посвященной этой библиотеке, английскую версию можно скачать на том же сайте, а если вам больше по душе русскоязычный текст, то добро пожаловать на http://kuzma.russofile.ru/ (перевод несколько косноязычен, но в целом хорош). Если вы внимательно читали мои прошлые статьи, посвященные simpleDB, то вы без труда разберетесь в том, как использовать и adodb. Дело в том, что библиотека может быть условно поделена на две части: первая из них посвящена уже привычным нам возможностям. Например, унифицированная система указания того, с какой СУБД мы хотим работать и необходимыми для подключения сведениями (имя, пароль, хост). Средства записи sql-запросов содержащих специальные placeholder-ы вместо которых подставляются значения с учетом экранирования спец. символов, добавления кавычек. Также средства позволяющие возвращать результат запроса в виде массивов php. Поэтому я не буду останавливаться на этой теме долго и просто приведу пару примеров. Прежде всего, подключите библиотеку и создайте объект подключения к СУБД, здесь обратите внимание на параметр, передаваемый внутрь ADONewConnection – имя используемой СУБД. Следующий шаг – подключение к серверу. Строка, подаваемая на вход функции Connect или PConnect, зависит от СУБД. Например, для mysql она будет выглядеть так:
$conn->Connect(‘сервер’, 'пользователь', 'пароль, 'база данных’)
$driver://$username:$password@hostname/$database?options[=value]Например, для mysql эта строка DSN будет выглядеть так (пароль в примере пустой):
$conn = &ADONewConnection('mysql://root:@localhost/smf');
adodb-errorhandler.inc.php adodb-errorpear.inc.php adodb-exceptions.inc.phpПрежде всего, если вы пишите под php5, то рекомендуется использовать исключения. Вы подключаете файл adodb-exceptions.inc.php и весь код по работе с СУБД помещаете внутрь секции try catch. Естественно, что выбрасываемый объект ADODB_Exception производен от стандартного для php родового класса всех исключений Exception и вы можете легко узнать что именно случилось и где. Например, так:
include_once('../adodb/adodb.inc.php');
// подключаем библиотеку adodb
include_once('../adodb/adodb-exceptions.inc.php');
// подключаем схему обработки ошибок основанную на исключениях
//$conn = &ADONewConnection('mysql');
// создаем подключение, обратите внимание на то, что в качестве параметра указывается mysql
//$conn->Connect('localhost', 'root', '', 'smf'); // выполняем соединение с СУБД
// а теперь второй способ подключения основанный на DSN
try{
$conn = &ADONewConnection('mysql://root:@localhost/smf1');
// создаем подключение, обратите внимание на то, что в качестве параметра указывается DSN
}
catch (exception $e){
print_r($e); // выводим все содержимое объекта исключения
adodb_backtrace($e->gettrace());// выводим trace вызовов функций приведших к ошибке
die ($e->getMessage ()); // вводим текст сообщения об ошибке и завершаем скрипт
}
$conn = &ADONewConnection('mysql://root:@localhost/smf1');
if (! $conn){
$e = ADODB_Pear_Error();
die('<p>error-connect:'. $e->message . '</p>'); }
$conn->debug = true;

После подключения к серверу вы должны посылать команды на отбор или модификацию данных. Для этого служит функция Execute. Первым параметром функции идет строка sql, внутри которой помечены точки вставки данных с помощью “?” (точно как и в simpleDB), переданных как второй параметр.
$recordSet = &$conn->Execute('select * from users where fio = ? or friends > ?', array('bill', 2));
$recordSet = &$conn->Execute('select * from users where fio = :fio or friends > :friends',
array(‘fio’ => 'bill', ‘friends’ => 2)
);
Команда Execute позволяет не только посылать команды на отбор данных, но и на их модификацию, в этом случае приятно использовать механизм bulk binding. Под этим грозным названием скрывается довольно простая идея: передать как список подставляемых переменных несколько массивов, например, так:
$arr = array(
array('Bill',12),
array('George', 4),
array('Lisa', 7));
$inserted = $conn->Execute('insert into users (fio,friends) values (?,?)',$arr);
print 'auto_increment id:' . $conn->Insert_ID ();
Если шел запрос на выборку данных, то отобранные записи будут помещены внутрь объекта ADORecordSet, методы которого перечислены далее:
EOF – здесь хранится признак того до конца всех найденных записей
Fields – массив в котором хранятся значения текущей записи. Вид массив определяется изменением конфигурационной переменной “$conn->setFetchMode(…)”. В качестве параметра указывается константы ADODB_FETCH_NUM или же ADODB_FETCH_ASSOC или же ADODB_FETCH_BOTH. Точь-в-точь как во встроенной библиотеке php/mysql указывается будут ли поля помещены в массив на основе их порядковых номеров, или же массив-запись будет ассоциативным с ключами равными именам полей, третий вариант – комбинация первых двух.
RecordCount – функция возвращает количество строк полученных в результате выполнения запроса.
MoveNext – приводит к переходу к следующей записи, так что в массив fields помещается следующая порция информации, здесь же меняется значения “флажка” EOF.
Далее приводится пример использования ADORecordSet:
// отправили запрос на сервер
$res = $conn->Execute("SELECT * FROM users");
while (!$res->EOF) {
// цикл до тех пор пока не кончатся отобранные записи
print "fio = ".$res->fields['fio']."\n";
// распечатыаем значения полей
print "friends = ".$res->fields['friends']."\n";
print "sex = ".$res->fields['sex'];
// не забываем переместиться к следующей записи
$res->MoveNext();
}
$conn->cacheSecs = 3600*24;// устанавливаем срок хранения информации в КЭШе в 24 часа
$rs = $conn->CacheExecute('select * from table');
// делаем запрос, только если информации нет в КЭШе то она и будет взята из СУБД
$all = $conn->getAll(‘select * from users’);
Итак, если у меня есть таблица со сведениями о людях:
CREATE TABLE `users` (
`id` int NOT NULL AUTO_INCREMENT,
`fio` varchar(50),
`sex` enum('f','m'),
`weight` float,
`friends` int,
PRIMARY KEY (`id`)
)
include_once('../adodb/adodb.inc.php'); // подключаем библиотеку adodb
include_once('../adodb/adodb-errorhandler.inc.php');
require_once('adodb/adodb-active-record.inc.php');
$db = NewADOConnection('mysql://root:@localhost/smf');
// привязываем ActiveRecord подсистему к конкретному соединению с БД
ADOdb_Active_Record::SetDatabaseAdapter($db);
// создаем класс соотвествующий таблице Users
class Users extends ADOdb_Active_Record{}
// в качестве параметра конструктора передается имя таблицы
$vasya = new Users('users');
$vasya->fio = 'Antony';// здесь мы назначаем свойства объекту
$vasya->sex = 'm';
$vasya->friends = '13';
$vasya->weight = 80;
$vasya->Save ();// сохраняем запись в БД

На сегодня хватит. В следующий раз я продолжу рассказ о возможностях adodb и паттерне activerecord.
« Доступ к базам данных из php. Часть 4 | Доступ к базам данных из php. Часть 6 » |