Смекни!
smekni.com

Обработка ошибок в коде программ РНР (стр. 7 из 8)

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

3.10 ТРАНСФОРМАЦИЯ ОШИБОК

Мы разделили все ошибки на два вида:

● "несерьезные" - диагностические сообщения; перехватываются при помощи set_error_handier();

● "серьезные" - невозможно продолжить нормальный ход работы кода, представлены исключениями.

Мы также отмечали, что, эти два вида ошибок не пересекаются и в идеале должны обрабатываться независимыми механизмами (ибо имеют различные подходы к написанию кода восстановления).

Известно, что в программировании любая ошибка может быть усилена, по крайней мере, без ухудшения качества кода. Например, если заставить РНР немедленно завершать работу скрипта не только при обнаружении ошибок классаE_ERROR и E_PARSE (перехват которых вообще невозможен), но также и при возникновении E_WARNING и даже E_NOTICE, программа станет более "хрупкой" к неточностям во входных данных. Но зато программист будет просто вынужден волей-неволей писать более качественный код, проверяющий каждую мелочь при своей работе. Таким образом, качество написания кода при "ужесточении" реакции на ошибку способно только возрасти, а это обычно является большим достоинством.


3.10.1 Серьезность "несерьезных" ошибок

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

Для примера рассмотрим сообщение класса E_WARNING, возникающее при ошибке открытия файла. Является ли оно фатальным, и возможно ли дальнейшее выполнение программы при его возникновении без каких-либо ветвлений? Однозначного ответа на этот вопрос дать нельзя.

Вот две крайние ситуации.

● Невозможность открытия файла крайне фатальна. Например, пусть скрипт открывает какой-нибудь файл, содержащий программный код, который необходимо выполнить (такие ситуации встречаются при модульной организации сценариев). При невозможности запуска этого кода вся дальнейшая работа программы может стать попросту бессмысленной.

● Невозможность открытия файла практически ни на что не влияет. К примеру, программа может записывать в этот файл информацию о том, когда она была запущена. Или даже более простой пример: скрипт просто проверяет, существует ли нужный файл, а если его нет, то создает новый пустой.

Рассмотрим теперь самое "слабое" сообщение, класса E_NOTICE, которое генерируется РНР, например, при использовании неинициализированной переменной. Часто такие ошибки считают настолько незначительными, что даже отключают реакцию на них в файле php.ini(error_reporting=E_ALL~E_NOTICE). Более того, именно такое значение error_reporting выставляется по умолчанию в дистрибутиве PHP.

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

Предположим, вы исполняете SQL-запрос для добавления новой записи в таблицу MySQL:

INSERT INTO table (id, parent_id, text)

VALUES (NULL, '$pid', 'Have you ever had a dream, that you were so sure was real?')

В переменной $pid хранится некоторый идентификатор, который должен быть обязательно числовым. Если эта переменная окажется неинициализированной (например, где-то в программе выше произошла опечатка), будет сгенерирована ошибка E_NOTICE, а вместо $pid подставится пустая строка. SQL-запрос же все равно останется синтаксически корректным. В результате в базе данных появится запись с полем parent_id, равным нулю (пустая строка '' без всяких предупреждений трактуется MySQL как 0). Это значение может быть недопустимым для поля parent_id (например, если оно является внешним ключом для таблицы table, т. е. указывает на другую "родительскую" запись с определенным ID). А раз значение недопустимо, то целостность базы данных нарушена, и это в дальнейшем вполне может привести к серьезным последствиям (заранее непредсказуемым) в других частях скрипта, причем об их связи с одним-единственным E_NOTICE, сгенерированным ранее, останется только догадываться.

● Теперь о том, когда E_NOTICE может быть безвредной. Вотпримеркода:

cinput type="text" name "field"

value="<?=htmlspecialchars($_REQUEST['field'])?>">

Очевидно, что если ячейка $_REQUEST['field'] не была инициализирована (например, скрипт вызван путем набора его адреса в браузере и не принимает никаких входных данных), элемент формы должен быть пуст. Подобная ситуация настолько широко распространена, что обычно ставят @ перед обращением к элементу массива, или даже перед htmlspecialchars(). В этом случае сообщение будет точно подавлено.

3.10.2 Преобразование ошибок в исключения

Мы приходим к выводу, что ошибку любого уровня можно трактовать как "серьезную" (за исключением ситуации, когда перед выражением явно указан оператор @, подавляющий вывод всех ошибок. Для обработки же серьезных ошибок в РНР имеется прекрасное средство — исключения.

Пример. Решение, которое мы здесь рассмотрим, — библиотека для автоматического преобразования всех перехватываемых ошибок РНР (вроде E_WARNING, E_NOTICE и т. д.) в объекты-исключения одноименных классов. Таким образом, если программа не сможет, например, открыть какой-то файл, теперь будет сгенерировано исключение, которое можно перехватить в соответствующем участке программы. Листинг 3.11 иллюстрирует сказанное.

Листинг 3.11. Файл w2e_simple.php

<?php ## Преобразование ошибок в исключения.

require_once "lib/config.php";

require_once "PHP/Exceptionizer.php";

// Для большей наглядности поместим основной проверочный код в функцию.

suffer();

// Убеждаемся, что перехват действительно был отключен.

echo "<b>Дальше должно идти обычное сообщение PHP.</b>";

fopen("fork", "r");

function suffer() {

// Создаем новый объект-преобразователь. Начиная с этого момента

// и до уничтожения переменной $w2e все перехватываемые ошибки

// превращаются в одноименные исключения.

$w2e = new PHP_Exceptionizer(E_ALL);

try {

// Открываем несуществующий файл. Здесь будет ошибка E_WARNING.

fopen("spoon", "r");

} catch (E_WARNING $e) {

// Перехватываем исключение класса E_WARNING.

echo "<pre><b>Перехвачена ошибка!</b>&bsol;n", $e, "</pre>";

}

// В конце можно явно удалить преобразователь командой:

// unset($w2e);

// Но можно этого и не делать - переменная и так удалится при

// выходе из функции (при этом вызовется деструктор объекта $w2e,

// отключающий слежение за ошибками).

}

?>

Обратите внимание на заголовок catch-блока. Он может поначалу ввести в заблуждение: ведь перехватывать можно только объекты-исключения, указывая имя класса, но никак не числовое значение (E_WARNING — вообще говоря, константа РНР, числовое значение которой равно 2 — можете убедиться в этом, запустив оператор echoE_WARNING). Тем не менее ошибки нет: E_WARNING — это одновременно и имя класса, определяемого в библиотеке PHP_Exceptionizer.

Заметьте также, что для ограничения области работы перехватчика используется уже знакомая нам идеология: "выделение ресурса есть инициализация". А именно в том месте, с которого необходимо начать преобразование, мы помещаем оператор создания нового объекта PHP_Exceptionizer и запоминаем последний в переменной, а там, где преобразование следует закончить, просто уничтожаем объект-перехватчик (явно или, как в примере, неявно, при выходе из функции).


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"));

}

// Вызывается при уничтожении объекта-перехватчика (например,

// при выходе его из области видимости функции). Восстанавливает

// предыдущий обработчик ошибок.

publicfunction __destruct() {

restore_error_handler();

}

}

/**

* Внутренний класс, содержащий метод перехвата ошибок.

* Мы не можем использовать для этой же цели непосредственно $this

* (класса PHP_Exceptionizer): вызов set_error_handler() увеличивает

* счетчик ссылок на объект, а он должен остаться неизменным, чтобы в

* программе всегда оставалась ровно одна ссылка.

*/

classPHP_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) {

// Если предыдущий обработчик существует, вызываем его.