$args = func_get_args();
call_user_func_array($this->prevHdl, $args);
} else {
// Иначе возвращаем false, что вызывает запуск встроенного
// обработчика PHP.
returnfalse;
}
}
// Возвращаем true (все сделано).
returntrue;
}
// Получаем текстовое представление типа ошибки.
$types = array(
"E_ERROR", "E_WARNING", "E_PARSE", "E_NOTICE", "E_CORE_ERROR",
"E_CORE_WARNING", "E_COMPILE_ERROR", "E_COMPILE_WARNING",
"E_USER_ERROR", "E_USER_WARNING", "E_USER_NOTICE", "E_STRICT",
);
// Формируем имя класса-исключения в зависимости от типа ошибки.
$className = __CLASS__ . "_" . "Exception";
foreach ($types as $t) {
$e = constant($t);
if ($errno & $e) {
$className = $t;
break;
}
}
// Генерируем исключение нужного типа.
throw new $className($errno, $errstr, $errfile, $errline);
}
}
/**
* Базовый класс для всех исключений, полученных в результате ошибки PHP.
*/
abstract class PHP_Exceptionizer_Exception extends Exception {
public function __construct($no=0, $str=null, $file=null, $line=0) {
parent::__construct($str, $no);
$this->file = $file;
$this->line = $line;
}
}
/**
* Создаем иерархию "серьезности" ошибок, чтобы можно было
* ловить не только исключения с указанием точного типа, но
* и сообщения, не менее "фатальные", чем указано.
*/
class E_EXCEPTION extends PHP_Exceptionizer_Exception {}
class AboveE_STRICT extends E_EXCEPTION {}
class E_STRICT extends AboveE_STRICT {}
class AboveE_NOTICE extends AboveE_STRICT {}
class E_NOTICE extends AboveE_NOTICE {}
class AboveE_WARNING extends AboveE_NOTICE {}
class E_WARNING extends AboveE_WARNING {}
class AboveE_PARSE extends AboveE_WARNING {}
class E_PARSE extends AboveE_PARSE {}
class AboveE_ERROR extends AboveE_PARSE {}
class E_ERROR extends AboveE_ERROR {}
class E_CORE_ERROR extends AboveE_ERROR {}
class E_CORE_WARNING extends AboveE_ERROR {}
class E_COMPILE_ERROR extends AboveE_ERROR {}
class E_COMPILE_WARNING extends AboveE_ERROR {}
class AboveE_USER_NOTICE extends E_EXCEPTION {}
class E_USER_NOTICE extends AboveE_USER_NOTICE {}
class AboveE_USER_WARNING extends AboveE_USER_NOTICE {}
class E_USER_WARNING extends AboveE_USER_WARNING {}
class AboveE_USER_ERROR extends AboveE_USER_WARNING {}
class E_USER_ERROR extends AboveE_USER_ERROR {}
// Иерархии пользовательских и встроенных ошибок не сравнимы,
// т.к. они используются для разных целей, и оценить
// "серьезность" нельзя.
?>
Перечислим достоинства описанного подхода.
● Ни одна ошибка не может быть случайно пропущена или проигнорирована. Программа получается более "хрупкой", но зато качество и "предсказуемость" поведения кода сильно возрастают.
● Используется удобный синтаксис обработки исключений, гораздо более "прозрачный", чем работа с set_error_handler(). Каждый объект-исключение дополнительно содержит информацию о месте возникновения ошибки, а также сведения о стеке вызовов функций; все эти данные можно извлечь с помощью соответствующих методов класса Exception.
● Можно перехватывать ошибки выборочно, по типам, например, отдельно обрабатывать сообщения E_WARNING и отдельно — E_NOTICE.
● Возможна установка "преобразователя" не для всех разновидностей ошибок, а только для некоторых из них (например, превращать ошибки E_WARNING в исключения класса E_WARNING, но "ничего не делать" с E_NOTICE).
● Классы-исключения объединены в иерархию наследования, что позволяет при необходимости перехватывать не только ошибки, точно совпадающие с указанным типом, но также заодно и более "серьезные".
Остановимся на последнем пункте приведенного выше списка. Взглянув еще раз в конец листинга 3.12, вы можете обнаружить, что классы-исключения объединены в довольно сложную иерархию наследования. Главной "изюминкой" метода является введение еще одной группы классов, имена которых имеют префикс Above. При этом более "серьезные" Above-классы ошибок являются потомками всех "менее серьезных". Например, AboveERROR, самая "серьезная" из ошибок, имеет в "предках" все остальные Above-классы, a AboveE_STRICT, самая слабая, не наследует никаких других Above-классов. Подобная иерархия позволяет нам перехватывать ошибки не только с типом, в точности совпадающим с указанным, но также и более серьезные.
Например, нам может потребоваться перехватывать в программе все ошибки класса E_USER_WARNING и более фатальные E_USER_ERROR. Действительно, если мы заботимся о каких-то там предупреждениях, то уж конечно должны позаботиться и о серьезных ошибках. Мы могли бы поступить так:
try {
// генерацияошибки
} catch (E_USER_WARNING $e) {
// кодвосстановления
} catch (E_USER_ERROR $e) {
// точно такой же код восстановления — приходится дублировать
}
Сложная иерархия исключений позволяет нам записать тот же фрагмент проще и понятнее (листинг3.13).
Листинг 3.13. Файл w2e_hier.php
<?php ## Иерархия ошибок.
require_once "lib/config.php";
require_once "PHP/Exceptionizer.php";
suffer();
function suffer() {
$w2e = new PHP_Exceptionizer(E_ALL);
try {
// Генерируем ошибку.
trigger_error("Damn it!", E_USER_ERROR);
} catch (AboveE_USER_WARNING $e) {
// Перехват ошибок: E_USER_WARNING и более серьезных.
echo "<pre><b>Перехвачена ошибка!</b>\n", $e, "</pre>";
}
}
?>
Использование механизма обработки исключений подразумевает, что после возникновения ошибки "назад ходу нет": управление передается в catch-блок, а нормальный ход выполнения программы прерывается. Возможно, вы не захотите такого поведения для всех типов предупреждений РНР. Например, ошибки класса E_NOTICE иногда не имеет смысла преобразовывать в исключения и делать их, таким образом, излишне фатальными.
Тем не менее, в большинстве случаев E_NOTICE свидетельствует о логической ошибке в программе и может рассматриваться, как тревожный сигнал программисту. Игнорирование таких ситуаций чревато проблемами при отладке, поэтому на практике имеет смысл преобразовывать в исключения и E_NOTICE тоже.
Вы можете указать в первом параметре конструктора PHP_Exceptionizer, какие типы ошибок необходимо перехватывать. По умолчанию там стоит E_ALL (т. е. перехватывать все ошибки и предупреждения), но вы можете задать и любое другое значение (например, битовую маску E_ALL~ E_NOTICE ~ E_STRICT), если пожелаете.
Существует еще и второй параметр конструктора. Он указывает, что нужно делать с сообщениями, тип которых не удовлетворяет битовой маске, приведенной в первом параметре. Можно их либо обрабатывать обычным способом, т. е. передавать ранее установленному обработчику (false), либо же попросту игнорировать (true).
Напомним, что в РНР 5 функция set_error_handler() принимает второй необязательный параметр, в котором можно указать битовую маску "срабатывания" обработчика. А именно для тех типов ошибок, которые "подходят" под маску, будет вызвана пользовательская функция, а для всех остальных— стандартная, встроенная в РНР. Класс PHP_Exceptionizer ведет себя несколько по-другому: в случае несовпадения типа ошибки с битовой маской будет вызван не встроенный в РНР обработчик, а предыдущий назначенный (если он имелся). Таким образом, реализуется стек перехватчиков ошибок. В ряде ситуаций это оказывается более удобным.
По неофициальным данным, в РНР версии 5.1 (и старше) разработчики планируют реализовать встроенный механизм преобразования ошибок в исключения. Для этого, предположительно, будет использоваться инструкция declare, позволяющая задавать блоку программы различные свойства (в том числе, что делать при возникновении ошибки). Код перехвата может выглядеть, например, так:
// Включаем "исключительное" поведение ошибок в РНР.
declare(exception_map='+standard:streams:*') {
try {
//В действительности генерируется исключение, а не предупреждение.
fopen("spoon", 'r');
} catch (Exception $e) {
if ($e->getCode() = = 'standard:streams:E_NOENT ') {
echo "Ложка не существует!";
}
}
}
// При выходе из declare-блока предыдущие свойства восстанавливаются.
К сожалению, в РНР версии 5.0 ничего подобного нет. Проверьте, возможно, данная функциональность появилась в вашей версии интерпретатора (см. документацию на инструкцию declare по адресу http://php.net/dedare).
Рассмотрена одна из самых важных и популярных при программировании задач — обработка ошибок в коде программы. Уточнено понятие термина "ошибка" и определена его роль в программировании, а также приведены различные классификации ошибочных ситуаций. Представлено описание понятия "исключение" и способы использования конструкции try...catch, а также описаны некоторые особенности ее работы в РНР. Описан механизм наследования и классификации исключений, использование которого может сильно сократить код программы и сделать его универсальным. Представлен код библиотеки, позволяющей обрабатывать многочисленные ошибки и предупреждения, генерируемые функциями РНР, как обыкновенные исключения.
Грамотный перехват ошибок с самого зарождения программирования считался трудной задачей. Механизм обработки исключений, хотя и упрощает ее, но все равно остается весьма сложным.
1. Скляр Д., Трахтенберг А. PHP. Сборник рецептов. – Пер. с англ. – СПб: Символ – Плюс, 2005. – 627 с., ил.
2. Котеров Д., Костарев А. PHP5 в подлиннике. – СПб: Символ – Плюс, 2005. – 1120 с., ил.
3. Дюбуа П. MySQL. Сборник рецептов. – Пер. с англ. - СПб: Символ – Плюс, 2004. – 1056 с., ил.
4. Томсон Лаура, Веллинг Люк. Разработка web – приложений на PHP и MySQL. – Пер. с англ. – СПб: ООО «ДиаСофтЮП», 2003. 672 с., ил.