« Доступ к базам данных из php. Часть 5 | Доступ к базам данных из php. Часть 7 » |
Доступ к базам данных из php. Часть 6
В прошлый раз я начал рассказ о паттернах Active Record и Row Data Gateway. Сегодня мы продолжим и завершим рассмотрение возможностей библиотеки adodb (ставшей стандартом де-факто и применяемой в разработке множества известных и не очень веб-приложений: postnuke, xaraya, moodle). Библиотеке, которая позволяет нам писать код быстрее и с меньшим числом ошибок. Также я уделю внимание вопросу оценки производительности вашего sql-кода.Прошлую статью я закончил простеньким примером кода использующий возможности adodb. Мы создали класс php, поля которого соответствовали колонкам таблицы mysql. Класс должен был наследоваться от встроенного в adodb класса ADOdb_Active_Record. На имя класса накладывается ограничение – оно должно быть построено на основе имени таблицы БД. Так для таблицы users, имя класса должно быть user. Это не всегда удобно, например, когда имена таблиц предваряются опциональным префиксом. В этом случае, укажите в качестве параметра конструктора имя таблицы. Или же можно установить значение специальной переменной $_table в составе объекта класса равной имени таблицы. Так три приведенных ниже способа создания новой записи являются равноценными:
include_once('../adodb/adodb.inc.php');// подключаем библиотеку adodb
require_once('../adodb/adodb-active-record.inc.php');
$db = NewADOConnection('mysql://root:@localhost/smf');
// привязываем ActiveRecord подсистему к конкретному соединению с БД
ADOdb_Active_Record::SetDatabaseAdapter($db);
// Вариант 1 - имя таблицы строится на основании имени класса
class User extends ADOdb_Active_Record {
}
// в качестве параметра конструктора передается имя таблицы
$obj_1 = new User();
$obj_1->fio = 'Billi';
$obj_1->Save ();
// Вариант 2 - имя таблицы задается как параметр конструктора
class Person extends ADOdb_Active_Record {
}
$obj_2 = new Person('users');
$obj_2->fio = 'Billi';
$obj_2->Save ();
// Вариант 3 - имя таблицы задается как значение поля класса _table
class Human extends ADOdb_Active_Record {
var $_table = 'users';
}
$obj_3 = new Human('users');
$obj_3->fio = 'Billi';
$obj_3->Save ();
print_r ($obj_3->GetAttributeNames ());
После создания объекта вы указываете значения его атрибутов и вызываете метод Save. Save сам определяет нужно ли добавить запись в таблицу или изменить уже существующую, например, так:
$obj_3 = new Human('users');
$obj_3->fio = 'Billi';
$obj_3->Save ();// Добавляем запись
$obj_3->fio = 'Willi';
$obj_3->Save ();// Обновляем запись
$if_ok = $obj_3->Save();
if (!$if_ok)
$err = $obj_3->ErrorMsg();

Естественно, что adodb должен содержать удобные средства для поиска информации в таблице. Например, я хочу найти запись о человеке, у которой значение поля fio равно “Billi”, и изменить дату рождения. Есть два метода умеющих искать записи: Load и Find. В качестве параметра Load передается строка, содержащая условие WHERE, например, так:
$obj_4 = new Human('users');
$obj_4->Load('fio = "billi"');
// второй вариант с placeholders
$obj_5 = new Human('users');
$obj_5->Load('fio = ?' , array ('Willi"and\'Ron'));
if ($obj_5->Load('fio like ? and weight between ? and ?' , array ('%Willi%', 500, 2000)))
print_r($obj_5);
else
print 'cannot Load record';
$list_of = $obj_5->Find('fio like ?' , array ('%Billi%'));
$db->StartTrans ();
// начали транзакцию
$obj_4 = new Human('users');
// меняем значения полей
$obj_4->Load('fio = "billi"');
$obj_4->sex = 'm';
$obj_4->weight = 120;
$obj_4->Save ();
// откатываем изменения
$db->RollbackTrans ();
Transactions not supported in 'mysql' driver. Use 'mysqlt' or 'mysqli' driverПослушно заменив в строке подключения к СУБД тип драйвера с mysql на mysqli
$db = NewADOConnection('mysqli://root:@localhost/smf');
Я заметил, что adodb просто-напросто отправляет на сервер команды BEGIN, COMMIT, ROLLBACK при вызове методов StartTrans, CompleteTrans, RollbackTrans. Соответственно, если СУБД не поддерживает транзакции, то и ADODB вам ничем не поможет.
Реализацию active record внутри adodb не стоит воспринимать как очередную “серебряную пулю”. В действительности стиль работы active record бывает слишком “примитивным” по сравнению с отправкой классических sql-запросов. Например, для того чтобы всем сотрудникам некоторого отдела увеличить зарплату на 20% на sql достаточно записать следующую строку:
UPDATE users SET salary = salary * 1.2 WHERE otdel = ‘marketing’;
$list_of = $obj_5->Find('otdel = ?' , array ('management'));
for ($i = 0; $i < count($list_of); $i++){
$list_of [$i]->salary = $list_of [$i]->salary * 1.2;
$list_of [$i]->Save ();
}
Производительность, скорость, эффективность – признайтесь, все мы знаем значение этих слов, но в действительности уделяем ли мы при разработке кода достаточное внимание этим факторам? Скорее нет, чем да. Причины банальны: в большинстве разрабатываемых приложений (особенно для типовых сайтов-визиток или каталогов) нет ни достаточно больших объемов данных, ни высоких нагрузок (десятков тысяч клиентов жаждущих попасть на ваш сайт). Не говоря о столь банальной причине как лишние затраты на разработку. Поэтому о факторе производительности вспоминают только тогда, когда проект вырастает из “коротких штанишек”, растет посещаемость, увеличивается объем базы данных и в один не прекрасный день сайт просто перестает грузиться. К вам приходят письма с жалобами от хостеров на повышенную нагрузку, а также письма от клиентов, которые не смогли попасть на сайт. Сделать проект, который будет готов по мере необходимости масштабироваться (без страшных переделок почти всего старого кода) довольно тяжело, дорого и наверняка избыточно. А вот сделать прикидку на будущее заполнив спланированную модель данных (таблицы, связи, индексы) большим количеством записей и посмотреть как быстро будет работать код поиска, сортировок и отбора информации для последующей генерации таблицы очень легко и занимает совсем немного времени. Благо, любая серьезная СУБД представляет средства для профилирования запросов, позволяет понять, как сервер будет выполнять вашу команду SQL, посмотреть план запроса и в случае необходимости внести правки в модель данных: добавить пару индексов, “слить” несколько таблиц в одну (денормализация). Самое дорогое – это не программа или код сайта: их можно переписать, фактически с нуля, самое плохое - ошибиться в планировании модели данных это может грозить потерей если не всей то, части информации в ходе миграции со старой модели данных на новую.
Давайте посмотрим, какие средства предусмотрены в составе adodb для мониторинга за выполняемыми запросами?
Так что, только в составе adodb есть средства для мониторинга за состоянием mysql? Нет, конечно, есть множество специализированных средств для решения этой задачи. В простейшем случае на сайте mysql.com вы можете скачать утилиту “MySQL Administrator”. Частью ее возможностей (кроме управления правами доступа, возможностей управлять backup-ами БД, удобных, действительно, удобных и понятных средств редактирования конфигурационных файлов mysql) является средства мониторинга. На закладе Health вы видите график загруженности mysql: количество обслуживаемых запросов в единицу времени, количество активных соединений и трафик. Выводятся также и сведения об эффективности работы в mysql КЭШа запросов.

Единственная сложность в том, что часто хостинги блокируют соединения с mysql с посторонних машин сети (не принадлежащих хостеру). Кроме того имеет смысл оценивать производительность в комплексе т.е. с учетом затрат которые вносит само adodb, а не только “чистое” время выполнения запроса на сервере. Как ожидалось, Adodb не осталась в стороне от вопроса мониторинга. Так в ее состав входит компонент, формирующий html-страницу для оценки основных параметров производительности. Все запросы, которые обрабатываются adodb, могут быть сохранены в специальный журнал – таблицу с именем adodb_logsql. Эта таблица будет автоматически создана, при первом вызове метода LogSQL (true) для объекта соединения. В таблице хранятся сведения о дате и времени выполнения запроса, собственно тексте запроса, значениях bind-переменных и времени выполнения запроса. Отделение текста запроса от подставляемых в него параметров очень важно т.к. дает возможность собирать находить одинаковые запросы и получить статистические сведения о том, какие из них заняли больше всего времени. После чего, вооружившись командой EXPLAIN, вы можете понять то, как выполняет запрос сервер, и подсказать ему лучший алгоритм, например, добавив нужные индексы. В следующем примере показано как включить механизм журналирования запросов. Единственная недоработка заключается в том, что adodb пытается записывать сведения в таблицу-журнал без предварительной проверки того, существует ли таблица. Поэтому если вы включили обработку ошибок, то первая же попытка сделать запись в журнал приведет к аварийному завершению всего скрипта.
// отключаем обработку ошибок
//include_once('../adodb/adodb-errorhandler.inc.php');
// подключаем модуль анализа производительности
include_once('../adodb/adodb-perf.inc.php');
// данный прием позволяет переопределить имя таблицы,
// в которую будет сохраняться информация журнала
adodb_perf::table('my_logsql_table');
// создаем подключение
$db = NewADOConnection('mysqli://root:@localhost/smf');
$db->debug = true;
// включаем режим журналирования и выводим на экран старое значение данного параметра
print_r ($db->LogSQL (true));
//Теперь после накопления статистики мы можем написать очень короткий скрипт отображающий
// html-интерфейс для доступа к накопленным сведениям.
$perf = NewPerfMonitor($db);// создаем объект визуализации счетчиков
echo $perf->SuspiciousSQL();
echo $perf->ExpensiveSQL();

При вызове методов ExpensiveSQL и SuspiciousSQL можно указать в качестве параметра число – сколько записей самых “дорогостоящих” и “подозрительных” (запросы, чье среднее время выполнения достаточно велико) запросов отобразить на экране браузера. Также в составе PerfMonitor есть методы. HealthCheck – выводит сведения об размере КЭШа и частоте попадания в него, также число подключений и их предельное значение. Следующий метод InvalidSQL – печатает таблицу с перечислением тех запросов, которые не удалось выполнить из-за каких либо ошибок, например, нарушения ограничений на значения полей таблицы. Естественно, что это не все возможности adodb, но все же дальнейший рассказ о нем я прекращу. Дело в том, что adodb местами напоминает свалку, где среди множества полезных вещей встречаются и предметы с непонятным назначением и довольно дрянного качества. К их числу я отношу возможность создать веб-интерфейс с paging-ом содержимого таблицы. Также возможность экспорта информации в csv-формат. Не радует и функция rs2html – формирующая html-таблицу с содержимым таблицы БД. С другой стороны, может быть полезным механизм Pivot Tables – возможность создания перекрестных таблиц, если конечно его доведут до ума. Средства кэширования нуждаются в тщательной проработке, как и многое другое.
Напоследок, я приведу прототип организации работы с adodb activerecord, который я часто использую у себя в работе. Его назначение – создать незаметную прослойку между adodb и нашим кодом php так, чтобы вносимую в базу данных информацию проверять на предмет корректности и сложных отношений. Не всегда возможно реализовать контроль за информацией только средствами СУБД. Т.к. не у всех СУБД есть хранимые процедуры, и часто код ее слишком сложен, для проверки могут потребоваться данные не доступные из СУБД (конфигурационные файл). Можно попробовать пожертвовать скоростью работы приложения в стремлении повысить ее “подхватываемость”. Под этим странным термином я понимаю задачу не потерять темпы развития проекта при смене части команды.
// создаем интерфейс валидатора, всякий код знающий о правилах накладываемых на информацию должен
// реализовывать данный интерфейс
interface ActiveValidator {
public function Validate ($rec_obj);
}
class Validate_Of_ADOdb_Active_Record extends ADOdb_Active_Record {
// статический массив объектов валидаторов
private static $validators = array ();
public static function addValidator (ActiveValidator $val){
self::$validators [] = $val;
}
public function Save (){
for ($i = 0; $i < count(self::$validators); $i++)
if (! self::$validators [$i]->Validate ($this))
// если хотя бы один тест провален - запись не сохраняется
return false;
ADOdb_Active_Record::Save ();
}
} // --- end of class ---
// объект валидатор основанный на длине некоторого поля
class LengthValidator implements ActiveValidator {
private $max_len_of;
private $field_name;
public function __construct ($max_len_of, $field_name){
$this->max_len_of = $max_len_of;
$this->field_name = $field_name;
}
public function Validate ($rec_obj){
$tmp = get_object_vars ($rec_obj);
return strlen($tmp[$this->field_name]) < $this->max_len_of;
}
} // ---- end of class ---
class User extends Validate_Of_ADOdb_Active_Record {
}
// привязываем к класу User объект валидатор свойств
User::addValidator(new LengthValidator (20, 'fio'));
// значение поля fio должно не превосходить 20 символов
$obj_1 = new User();
$obj_1->fio = 'Billi';
$obj_1->Save ();
//здесь прозрачно для нас срабатывает валидация и объект не
//будет помещен в базу, если хотя бы один из присоединенных тестов будет провален.
« Доступ к базам данных из php. Часть 5 | Доступ к базам данных из php. Часть 7 » |