скачать рефераты
  RSS    

Меню

Быстрый поиск

скачать рефераты

скачать рефератыУчебное пособие: Обработка ошибок в коде программ РНР

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

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

Известно, что в программировании любая ошибка может быть усилена, по крайней мере, без ухудшения качества кода. Например, если заставить РНР немедленно завершать работу скрипта не только при обнаружении ошибок класса 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>\n", $e, "</pre>";

 }

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

 // unset($w2e);

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

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

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

}

?>

Обратите внимание на заголовок catch-блока. Он может поначалу ввести в заблуждение: ведь перехватывать можно только объекты-исключения, указывая имя класса, но никак не числовое значение (E_WARNING — вообще говоря, константа РНР, числовое значение которой равно 2 — можете убедиться в этом, запустив оператор echo E_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"));

 }

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

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

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

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


Страницы: 1, 2, 3, 4, 5


Новости

Быстрый поиск

Группа вКонтакте: новости

Пока нет

Новости в Twitter и Facebook

  скачать рефераты              скачать рефераты

Новости

скачать рефераты

Обратная связь

Поиск
Обратная связь
Реклама и размещение статей на сайте
© 2010.