1. Объект DBQuery
Теперь наш объект DBQuery просто эмулирует хранимую процедуру — после выполнения она возвращает ресурс результата, который необходимо сохранить, и если вы хотите использовать функции для набора результатов (например, num_rows() или fetch_row() ); ), вы должны передать объект MySqlDB. Итак, каков же эффект, если объект DBQuery реализует функции, реализованные объектом MySqlDB (который предназначен для работы с результатами выполненного запроса)? Давайте продолжим использовать код из предыдущего примера и предположим, что наши ресурсы результатов теперь управляются объектом DBQuery; Исходный код класса DBQuery показан в листинге 1.
Листинг 1. Использование класса DBQuery.
требуется «mysql_db.php»;
require_once 'query.php';
$db = новый MySqlDb;
$db->connect('хост', 'имя пользователя', 'пароль');
$db->query('use content_management_system');
$query = новый DBQuery($db);
$query->prepare('SELECT fname,sname FROMusers WHERE username=:1S AND pword=:2S AND expire_time<:3I');
пытаться {
if($query->execute("visualad", "apron", time()))->num_rows() == 1) {
echo('Правильные учетные данные');
} еще {
echo('Неверные учетные данные / Срок действия сеанса истек');
}
} catch (QueryException $e) {
echo('Ошибка выполнения запроса:' . $e);
}
В приведенном выше модифицированном коде нас больше всего интересуют операторы catch и оператор выполнения.
· Оператор выполнения больше не возвращает ресурс результата, теперь он возвращает сам объект DBQuery.
· Объект DBQuery теперь реализует функцию num_rows(), с которой мы уже знакомы по интерфейсу БД.
· Если выполнение запроса завершается неудачей, выдается исключение типа QueryException. При преобразовании в строку возвращается подробная информация о произошедшей ошибке.
Для этого вам необходимо использовать прокси. Фактически, вы уже используете прокси в нашем объекте DBQuery, но теперь вы будете использовать его более подробно, чтобы тесно привязать его к объекту MySqlDB. Объект DBQuery был инициализирован с помощью объекта, реализующего интерфейс БД, и он уже содержит функцию-член выполнения, которая вызывает метод query() объекта БД для выполнения запроса. Сам объект DBQuery фактически не запрашивает базу данных, он оставляет эту задачу объекту DB. Это прокси, представляющий собой процесс, с помощью которого объект может реализовать определенное поведение, отправляя сообщения другому объекту, реализующему такое же или похожее поведение.
Для этого вам необходимо изменить объект DBQuery, включив в него все функции, которые работают с результирующим ресурсом из объекта БД. Вам необходимо использовать сохраненные результаты при выполнении запроса для вызова соответствующей функции объекта БД и возврата ее результатов. Будут добавлены следующие функции:
Листинг 2: Расширение класса DBQuery с помощью прокси.
классDBQuery
{
.....
публичная функция fetch_array()
{
если (! is_resource($this->result)) {
throw new Exception('Запрос не выполнен.');
}
вернуть $this->db->fetch_array($this->result);
}
Публичная функция fetch_row()
{
если (! is_resource($this->result)) {
throw new Exception('Запрос не выполнен.');
}
вернуть $this->db->fetch_row($this->result);
}
Публичная функция fetch_assoc()
{
если (! is_resource($this->result)) {
throw new Exception('Запрос не выполнен.');
}
вернуть $this->db->fetch_assoc($this->result);
}
Публичная функция fetch_object()
{
если (! is_resource($this->result)) {
throw new Exception('Запрос не выполнен.');
}
вернуть $this->db->fetch_object($this->result);
}
Публичная функция num_rows()
{
если (! is_resource($this->result)) {
throw new Exception('Запрос не выполнен.');
}
вернуть $this->db->num_rows($this->result);
}
}
Реализация каждой функции довольно проста. Сначала он проверяет, был ли выполнен запрос, затем делегирует задачу объекту БД, возвращая результаты, как если бы это был сам объект запроса (называемый базовой функцией базы данных).
2. Подсказка типа.
Чтобы прокси работал, нам необходимо убедиться, что переменная $db объекта DBQuery является экземпляром объекта, реализующего интерфейс БД. Подсказки типов — это новая функция PHP 5, которая позволяет вам приводить параметры функции к объектам определенного типа. До PHP 5 единственным способом убедиться, что параметр функции относится к определенному типу объекта, было использование функции проверки типа, предусмотренной в PHP (то есть is_a()). Теперь вы можете просто привести тип объекта, указав перед параметром функции имя типа. Вы уже видели подсказки типов в нашем объекте DBQuery, который гарантирует, что объект, реализующий интерфейс БД, будет передан в конструктор объекта.
публичная функция __construct(DB $db)
{
$this->db = $db;
}
При использовании подсказок типов вы можете указывать не только типы объектов, но также абстрактные классы и интерфейсы.
3. Вызывайте исключения.
Из приведенного выше кода вы могли заметить, что вы ловите исключение под названием QueryException (мы реализуем этот объект позже). Исключение похоже на ошибку, но более общего характера. Лучший способ описать исключение — использовать аварийную ситуацию. Хотя чрезвычайная ситуация не может быть «фатальной», ее все равно необходимо принять. Когда в PHP генерируется исключение, текущая область выполнения быстро завершается, будь то функция, блок try..catch или сам скрипт. Затем исключение проходит через стек вызовов, завершая каждую область выполнения, пока оно либо не будет перехвачено блоком try..catch, либо не достигнет вершины стека вызовов, после чего генерируется фатальная ошибка.
Обработка исключений — еще одна новая функция PHP 5. При использовании в сочетании с ООП она позволяет обеспечить хороший контроль над обработкой ошибок и отчетами. Блок try..catch — важный механизм обработки исключений. После обнаружения исключение продолжится со следующей строки кода, где оно было перехвачено и обработано.
Если запрос не выполнен, вам необходимо изменить функцию выполнения, чтобы она вызывала исключение. Вы создадите собственный объект исключения с именем QueryException — ему будет передан объект DBQuery, вызвавший ошибку.
Листинг 3. Вызывает исключение.
/**
*Выполнить текущий запрос
*
* Выполнить текущий запрос — заменяя все точки предоставленными аргументами.
* .
*
* @parameters: смешанные параметры запроса $queryParams,...
* @return: Ресурс A — ссылка, описывающая ресурс, на котором выполняется запрос.
*/
выполнение публичной функции ($queryParams = '')
{
//Например: SELECT * FROM table WHERE name=:1S AND type=:2I AND level=:3N
$args = func_get_args();
если ($this->stored_procedure) {
/*Вызов функции компиляции, чтобы получить запрос*/
$query = call_user_func_array(array($this, 'compile'), $args);
} еще {
/*Хранимая процедура не инициализирована, поэтому выполняется как стандартный запрос*/
$query = $queryParams;
}
$result = $this->db->query($query);
если (! $результат) {
выбросить новое QueryException($this);
}
$this->result = $result;
/* Обратите внимание, что теперь мы возвращаем сам объект, что позволяет нам вызывать функции-члены из результата, возвращаемого этой функцией */
вернуть $это;
}
4. Используйте наследование для создания пользовательских исключений
. В PHP в качестве исключения можно создать любой объект, однако сначала исключение должно наследовать от встроенного класса исключений PHP; Создав собственное исключение, вы можете зарегистрировать другую информацию об ошибке, создать запись в файле журнала или делать все, что захотите. Ваше пользовательское исключение будет выполнять следующие действия:
· Записывать сообщение об ошибке из объекта БД, созданного запросом.
· Предоставьте точную информацию о строке кода, в которой произошла ошибка запроса, — путем изучения стека вызовов.
· Отображать сообщения об ошибках и текст запроса — при преобразовании в строку.
Чтобы получить сообщение об ошибке и текст запроса, необходимо внести несколько изменений в объект DBQuery.
1. В класс необходимо добавить новый защищенный атрибут — compiledQuery.
2. Функция compile() обновляет свойство CompileQuery запроса, добавляя в него текст запроса.
3. Необходимо добавить функцию для получения скомпилированного текста запроса.
4. Также следует добавить функцию — она получает текущий объект БД, связанный с объектом DBQuery.
Листинг 4. Вызов исключения.
классDBQuery
{
/**
*Сохраните скомпилированную версию запроса после вызова compile() или Execute()*
* @var строка $compiledQuery
*/
защищенный $compiledQuery;
/**
* Возвращает скомпилированный запрос, не выполняя его.
* @parameters: смешанные $params,... параметры запроса* @return: string — скомпилированный запрос*/
компиляция общедоступной функции ($params='')
{
если (! $this->stored_procedure) {
throw new Exception("Хранимая процедура не инициализирована.");
}
/*замена параметров*/
$params = func_get_args(); //Получаем параметры функции $query = preg_replace("/(?compile_callback($params, 1, "2")', $this->query);
return ($this->compiledQuery = $this->add_strings($query) //Вводим строку обратно в запрос}
публичная функция getDB()
{
верните $this->db;
}
публичная функция getCompiledQuery()
{
вернуть $this->compiledQuery;
}
}
Теперь вы можете реализовать класс QueryException. Обратите внимание, как вы просматриваете стек вызовов, чтобы найти место в сценарии, которое фактически вызвало ошибку. Это именно тот случай, когда объект DBQuery, выдающий исключение, является подклассом, наследуемым от объекта DBQuery.
Листинг 5: Класс QueryException.
/**
*Исключение запроса
*
*При попытке выполнить запрос в случае возникновения ошибки объект {@link DBQuery} выдаст ошибку.
*/
класс QueryException расширяет исключение
{
/**
*Текст запроса*
* @var строка $QueryText;
*/
защищенный $QueryText;
/**
*Номер/код ошибки из базы данных*
* @var строка $ErrorCode
*/
защищенный $ErrorNumber;
/**
*Сообщение об ошибке из базы данных*
* @var строка $ErrorMessage
*/
защищенный $ErrorMessage;
/**
*Конструктор класса*
* @Parameter: DBQuery $db, объект запроса, выдающий исключение */
общедоступная функция __construct (DBQuery $query)
{
/*Получаем стек вызовов*/
$backtrace = $this->GetTrace();
/*Установим строку и файл в место, где фактически произошла ошибка*/
если (count($backtrace) > 0) {
$х = 1;
/*Если класс запроса унаследован, нам нужно игнорировать вызовы, сделанные подклассами*/
while((! isset($backtrace[$x]['line'])) ||
(isset($backtrace[$x]['class']) && is_subclass_of($backtrace[$x]['class'], 'DBQuery')) ||
(strpos(strtolower(@$backtrace[$x]['function']), 'call_user_func')) !== false ) {
/*Выполнение цикла, пока нет номера строки или если вызываемая функция является подклассом класса DBQuery*/
++$х;
/*Если мы достигаем нижней части стека, то используем первый вызывающий объект*/
if (($x) >= count($backtrace)) {
$x = счетчик ($backtrace);
перерыв;
}
}
/*Если приведенный выше цикл выполняется хотя бы один раз, мы можем уменьшить его на 1, чтобы найти фактическую строку кода, вызвавшую ошибку*/
если ($x != 1) {
$х -= 1;
}
/*Наконец, мы можем установить номера файлов и строк, которые должны отражать оператор SQL, вызвавший ошибку*/
$this->line = $backtrace[$x]['line'];
$this->file = $backtrace[$x]['file'];
}
$this->QueryText = $query->getCompiledQuery();
$this->ErrorNumber = $query->getDB()->errno();
$this->ErrorMessage = $query->getDB()->error();
/*Вызов конструктора исключений суперкласса*/
родитель::__construct('Ошибка запроса', 0);
}
/**
*получить текст запроса*
* @return строка текста запроса */
публичная функция GetQueryText()
{
верните $this->QueryText;
}
/**
*получил номер ошибки*
* @return string номер ошибки */
публичная функция GetErrorNumber()
{
вернуть $this->ErrorNumber;
}
/**
*получил сообщение об ошибке*
* Сообщение об ошибке @return string */
публичная функция GetErrorMessage()
{
вернуть $this->ErrorMessage;
}
/**
* Вызывается, когда объект преобразуется в строку.
* @return string */
публичная функция __toString()
{
$output = "Ошибка запроса в {$this->file} в строке {$this->line}nn";
$output .= "Запрос: {$this->QueryText}n";
$output .= "Ошибка: {$this->ErrorMessage} ({$this->ErrorNumber})nn"
;
}
}
Теперь код, который вы видели в начале этого раздела, работает.
5. Заключение
В этой статье вы увидели, как агент сопоставляет интерфейс БД, связанный с запросом, с операциями над конкретным результатом запроса. Объекты DBQuery предоставляют те же функции, такие как fetch_assoc(), что и объекты БД. Однако все они работают для одного запроса. Вы также узнали, как использовать пользовательские исключения, чтобы предоставить подробную информацию о том, когда и где произошла ошибка, и как они могут лучше контролировать обработку ошибок.