3.10.3 Код библиотеки PHP_Exceptionizer
Прежде чем продолжить описание возможностей перехвата, давайте рассмотрим код класса PHP_Exceptionizer, реализующего преобразование стандартных ошибок РНР в исключения (листинг.3.12).
Листинг 3.12. Файл lib/PHP/Exceptionizer.php
<?php ## Класс для преобразования ошибок PHP в исключения.
/**
* Класс для преобразования перехватываемых (см. set_error_handler())
* ошибок и предупреждений PHP в исключения.
*
* Следующие типы ошибок, хотя и поддерживаются формально, не могут
* быть перехвачены:
* E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR,
* E_COMPILE_WARNING
*/
class PHP_Exceptionizer {
// Создает новый объект-перехватчик и подключает его к стеку
// обработчиков ошибок PHP (используется идеология "выделение
// ресурса есть инициализация").
public function __construct($mask=E_ALL, $ignoreOther=false) {
$catcher = new PHP_Exceptionizer_Catcher();
$catcher->mask = $mask;
$catcher->ignoreOther = $ignoreOther;
$catcher->prevHdl = set_error_handler(array($catcher, "handler"));
}
// Вызывается при уничтожении объекта-перехватчика (например,
// при выходе его из области видимости функции). Восстанавливает
// предыдущий обработчик ошибок.
public function __destruct() {
restore_error_handler();
}
}
/**
* Внутренний класс, содержащий метод перехвата ошибок.
* Мы не можем использовать для этой же цели непосредственно $this
* (класса PHP_Exceptionizer): вызов set_error_handler() увеличивает
* счетчик ссылок на объект, а он должен остаться неизменным, чтобы в
* программе всегда оставалась ровно одна ссылка.
*/
class PHP_Exceptionizer_Catcher {
// Битовые флаги предупреждений, которые будут перехватываться.
public $mask = E_ALL;
// Признак, нужно ли игнорировать остальные типы ошибок, или же
// следует использовать стандартный механизм обработки PHP.
public $ignoreOther = false;
// Предыдущий обработчик ошибок.
public $prevHdl = null;
// Функция-обработчик ошибок PHP.
public function handler($errno, $errstr, $errfile, $errline) {
// Если error_reporting нулевой, значит, использован оператор @,
// и все ошибки должны игнорироваться.
if (!error_reporting()) return;
// Перехватчик НЕ должен обрабатывать этот тип ошибки?
if (!($errno & $this->mask)) {
// Если ошибку НЕ следует игнорировать...
if (!$this->ignoreOther) {
if ($this->prevHdl) {
// Если предыдущий обработчик существует, вызываем его.
$args = func_get_args();
call_user_func_array($this->prevHdl, $args);
} else {
// Иначе возвращаем false, что вызывает запуск встроенного
// обработчика PHP.
return false;
}
}
// Возвращаем true (все сделано).
return true;
}
// Получаем текстовое представление типа ошибки.
$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.10.4 Иерархия исключенийОстановимся на последнем пункте приведенного выше списка. Взглянув еще раз в конец листинга 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>";
}
}
?>
3.10.5 Фильтрация по типам ошибокИспользование механизма обработки исключений подразумевает, что после возникновения ошибки "назад ходу нет": управление передается в 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 ведет себя несколько по-другому: в случае несовпадения типа ошибки с битовой маской будет вызван не встроенный в РНР обработчик, а предыдущий назначенный (если он имелся). Таким образом, реализуется стек перехватчиков ошибок. В ряде ситуаций это оказывается более удобным.
3.10.6 ПерспективыПо неофициальным данным, в РНР версии 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 с., ил.
... от необходимости самим создавать соответствующие программы. Присутствует в ASP и PHP, отсутствует в XML. Создание серверных сценариев. Основа любого языка для создания динамических сайтов. Присутствует в ASP и PHP, отсутствует в XML. Описание данных. Важная функция, позволяющая представлять данные в едином формате, единым способом записи. Отсутствует в ASP и PHP, присутствует в XML. Наличие ...
... Apache на русском языке: Если открылась начальная страница, значит Apache установлен правильно. ● Перейдите в окно Web-сервера Apache с помощью Панели задач операционной системы Windows и завершите работу сервера с помощью кнопки [X] в правом верхнем углу окна. 1.3. Установка PHP Загрузить дистрибутивов PHP можно с официальной страницы http://www.php.net/downloads.php из секции Windows ...
... 20% от общего товарооборота, соответственно окупаемость магазина произойдет за более длинный промежуток времени, нежели чем при полном ассортименте. Получение дополнительной прибыли электронному магазину по продаже программного обеспечения, компакт дисков и dvd дисков на основании приведенных выше затрат ожидается уже с третьего - четвертого месяца, что является достаточно хорошим экономическим ...
... продукции, создавать новые рынки, расширять производство, изменять организационные структуры управления, обеспечивая их адаптивность к основным изменениям характеристики рынка и поведения потребителя. Использование автоматизированной системы продажи сотовых телефонов, которая включает в себя создание базы данных клиентов, дает возможность отслеживать потребности и приоритеты в выборе телефона ...
0 комментариев