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 с., ил.


Информация о работе «Обработка ошибок в коде программ РНР»
Раздел: Информатика, программирование
Количество знаков с пробелами: 79083
Количество таблиц: 1
Количество изображений: 0

Похожие работы

Скачать
85123
2
8

... от необходимости самим создавать соответствующие программы. Присутствует в ASP и PHP, отсутствует в XML. Создание серверных сценариев. Основа любого языка для создания динамических сайтов. Присутствует в ASP и PHP, отсутствует в XML. Описание данных. Важная функция, позволяющая представлять данные в едином формате, единым способом записи. Отсутствует в ASP и PHP, присутствует в XML. Наличие ...

Скачать
70189
1
170

... Apache на русском языке: Если открылась начальная страница, значит Apache установлен правильно. ● Перейдите в окно Web-сервера Apache с помощью Панели задач операционной системы Windows и завершите работу сервера с помощью кнопки [X] в правом верхнем углу окна. 1.3. Установка PHP Загрузить дистрибутивов PHP можно с официальной страницы http://www.php.net/downloads.php из секции Windows ...

Скачать
184575
9
11

... 20% от общего товарооборота, соответственно окупаемость магазина произойдет за более длинный промежуток времени, нежели чем при полном ассортименте. Получение дополнительной прибыли электронному магазину по продаже программного обеспечения, компакт дисков и dvd дисков на основании приведенных выше затрат ожидается уже с третьего - четвертого месяца, что является достаточно хорошим экономическим ...

Скачать
111078
12
21

... продукции, создавать новые рынки, расширять производство, изменять организационные структуры управления, обеспечивая их адаптивность к основным изменениям характеристики рынка и поведения потребителя. Использование автоматизированной системы продажи сотовых телефонов, которая включает в себя создание базы данных клиентов, дает возможность отслеживать потребности и приоритеты в выборе телефона ...

0 комментариев


Наверх