Смекни!
smekni.com

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

<?php ## Отключение ошибок: логи не модифицируются.

error_reporting(E_ALL);

ini_set("error_log", "log.txt");

ini_set("log_errors", true);

@filemtime("spoon");

?>

Запустив приведенный скрипт, вы заметите, что файл журнала log.txt даже не создался. Попробуйте теперь убрать оператор @ — вы- получите предупреждение "stat failed for spoon", и оно же запишется в log.txt.

1.5.1 ПРИМЕР ИСПОЛЬЗОВАНИЯ ОПЕРАТОРА @

Пусть имеется форма с submit-кнопкой, и нужно в сценарии определить, нажата ли она. Можно сделать это так:

<?php

if (!empty($submit)) echo "Кнопканажата!";

?>

Согласитесь, код листинга 1.2 элегантнее.

Листинг 1.2. Файл submit.php

<?php ## Удобство оператора @.

if (@$_REQUEST['submit']) echo "Кнопка нажата!"

?>

<form action="<?=$_SERVER['SCRIPT_NAME']?>">

<input type="submit" name="submit" value="Go!">

</form>

1.5.2 ПРЕДОСТЕРИЖЕНИЯ ПО ПРИМЕНЕНИЮ ОПЕРАТОРА ОТКЛЮЧЕНИЯ ОШИБОК @

Оператор @ следует применять с осторожностью. Например, следующий код никуда не годится — постарайтесь не повторять его в своих программах.

// Не подавляйте сообщения об ошибках во включаемых файлах — иначе

// отладка превратится в кромешный ад!

@include "mistake.php";

//Не используйте оператор @ перед функциями, написанными на РНР,

// если только нет 100%-й уверенности в том, что они работают

// корректно в любой ситуации!

@myOwnBigFunction() ;

Рекомендации, в каких случаях применение оператора подавления ошибок оправдано и относительно безопасно:

● в конструкциях if (@$_REQUEST[' key' ]) для проверки существования (и ненулевого значения) элемента массива;

● перед стандартными функциями РНР вроде fopen(), filemtime(), mysql_connect() и т. д., если далее идет проверка кода возврата и вывод сообщения об ошибке;

● в HTML-файлах со вставками PHP-кода, если очень лень писать много кавычек: <?=@$resuit [element] [field] ?> (такой вызов не породит ошибок, несмотря на отсутствие кавычек).

Во всех остальных случаях лучше несколько раз подумать, прежде чем применять оператор @. Чем меньше область кода, в которой он будет действовать, тем более надежной окажется программа. Поэтому не рекомендуется использовать @ перед include — это заблокирует проверку ошибок для очень большого фрагмента программы.


2. ПЕРЕХВАТ ОШИБОК. МЕТОД РЕГИСТРАЦИИ ОБРАБОТЧИКА ОШИБОК

В РНР версии 5 существуют два метода перехвата ошибок во время выполнения программы:

● регистрация обработчика ошибки.

● исключений;

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

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

Метод исключений, который рассмотрен в п. 3, лишен этого недостатка, он достаточно сложен и практически не реализован на уровне стандартных функций РНР.

Пример использования обработчика ошибок приведен в листинге 2.1.

Листинг 2.1. Файл handler1.php

<?php ## Перехват ошибок и предупреждений.

// Определяем новую функцию-обработчик.

function myErrorHandler($errno, $msg, $file, $line) {

// Если используется @, ничего не делать.

if (error_reporting() == 0) return;

// Иначе - выводимсообщение.

echo '<div style="border-style:inset; border-width:2">';

echo "Произошла ошибка с кодом <b>$errno</b>!<br>";

echo "Файл: <tt>$file</tt>, строка $line.<br>";

echo "Текстошибки: <i>$msg</i>";

echo "</div>";

}

// Регистрируем ее для всех типов ошибок.

set_error_handler("myErrorHandler", E_ALL);

// Вызываем функцию для несуществующего файла, чтобы

// сгенерировать предупреждение, которое будет перехвачено.

filemtime("spoon");

?>

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

2.1 ФУНКЦИЯset_error_handler

Функция

string set_error_handler(string $runcName [, int $errorTypes])

регистрирует пользовательский обработчик ошибок — функцию, которая будет вызвана при возникновении сообщений, указанных в $errorTypes типов (битовая маска, например, E_ALL~E_NOTICE).

Сообщения, не соответствующие маске $errorTypes, будут в любом случае обрабатываться встроенными средствами РНР, а не предыдущей установленной функцией-перехватчиком. Имя пользовательской функции передается в параметре $runcName. Если до этого был установлен какой-то другой обработчик, функция вернет его имя — с тем, чтобы его можно было позже восстановить. Пользовательский обработчик должен задаваться так, как показано в листинге 2.2.

Листинг 2.2. Файл handler0.php

<?php ## Перехват ошибок и предупреждений.

// Определяем новую функцию-обработчик.

function myErrorHandler($errno, $msg, $file, $line) {

echo '<div style="border-style:inset; border-width:2">';

echo "Произошла ошибка с кодом <b>$errno</b>!<br>";

echo "Файл: <tt>$file</tt>, строка $line.<br>";

echo "Текстошибки: <i>$msg</i>";

echo "</div>";

}

// Регистрируем ее для всех типов ошибок.

set_error_handler("myErrorHandler", E_ALL);

// Вызываем функцию для несуществующего файла, чтобы

// сгенерировать предупреждение, которое будет перехвачено.

filemtime("spoon");

?>

Теперь при возникновении любой ошибки или даже предупреждения в программе будет вызвана функция myErrorHandier(). Ее аргументы получат значения, соответственно, номера ошибки, текста ошибки, имени файла и номера строки, в которой было сгенерировано сообщение.

К сожалению, не все типы ошибок могут быть перехвачены таким образом. Например, ошибки трансляции во внутреннее представление E_PARSE, а также E_ERROR немедленно завершают работу программы. Вызовы функции die()также не перехватываются.

Назначить функцию реакции на E_PARSE и E_ERRORвсе же можно. Дело в том, что перехватчик выходного потока скрипта, устанавливаемый функцией ob__start(), обязательно вызывается при завершении работы программы, в том числе в случае фатальной ошибки. Конечно, ему не передается сообщение об ошибке и ее код; он должен сам получить эти данные из "хвоста" выходного потока (например, используя функции для работы с регулярными выражениями).

В случае если пользовательский обработчик возвращает значение false (и только его!), считается, что ошибка не была обработана, и управление передается стандартному обработчику РНР (обычно он выводит текст ошибки в браузер). Все остальные возвращаемые значения (включая даже null или, что то же самое, в случае, если оператора return вообще нет), приводят к подавлению запуска стандартной процедуры обработки ошибок.

2.2 ФУНКЦИЯrestore_error_handler()

void restore_error_handler()

Когда вызывается функция set_error_handler(), предыдущее имя пользовательской функции запоминается в специальном внутреннем стеке РНР. Чтобы извлечь это имя и тут же его установить в качестве обработчика, применяется функция restore_error_handler().Пример:

// Регистрируем обработчик для всех типов ошибок.

set_error_handler("myErrorHandler", E_ALL);

// Включаемподозрительныйфайл.

include "suspicious_file.php";

// Восстанавливаем предыдущий обработчик.

restore_error_handler();

Необходимо следить, чтобы количество вызовов restore_error_handler() было в точности равно числу вызовов set_error_handler().Нельзя восстановить то, чего нет.

2.3 ПРОБЛЕМЫ С ОПЕРАТОРОМ @

Пользовательская функция перехвата ошибок вызывается вне зависимости от того, был ли использован оператор подавления ошибок в момент генерации предупреждения. Это очень неудобно: поставив оператор @ перед вызовом filemtime(), мы увидим, что результат работы скрипта нисколько не изменился: текст предупреждения по-прежнему выводится в браузер.

Для того чтобы решить проблему, воспользуемся полезным свойством оператора @: на время своего выполнения он устанавливает error_reporting, равным нулю. Значит, мы можем определить, был ли вызван оператор @ в момент "срабатывания" обработчика, по нулевому значению функции error_reporting() (при вызове без параметров она возвращает текущий уровень ошибок) — листинг 2.3.

Листинг 2.3. Файл handler.php

<?php ## Перехват ошибок и предупреждений.

// Определяем новую функцию-обработчик.

functionmyErrorHandler($errno, $msg, $file, $line) {

// Если используется @, ничего не делать.

if (error_reporting() == 0) return;

// Иначе - выводим сообщение.

echo '<div style="border-style:inset; border-width:2">';

echo "Произошла ошибка с кодом <b>$errno</b>!<br>";

echo "Файл: <tt>$file</tt>, строка $line.<br>";

echo "Текст ошибки: <i>$msg</i>";

echo "</div>";

}

// Регистрируем ее для всех типов ошибок.

set_error_handler("myErrorHandler", E_ALL);

// Вызываем функцию для несуществующего файла, чтобы

// сгенерировать предупреждение, которое будет перехвачено.

@filemtime("spoon");

?>

Теперь все работает корректно: предупреждение не отображается в браузере, т. к. применен оператор @.


2.4 ГЕНЕРАЦИЯОШИБОК

Функция

void trigger_error(string $message [, int $type])

предназначена для искусственной генерации сообщения об ошибки с указанным типом. В РНР существует несколько констант, чьи имена начинаются с E_USER_, которые можно использовать наравне с E_ERROR, E_WARNING и т. д., но только для персональных нужд. Именно с функцией trigger_error() они чаще всего и применяются. Вот эти константы:

E_ERROR

E_WARNING

E_USER_NOTICE

Как их использовать — целиком зависит от программиста: никаких ограничений не налагается.

int error_log(string $msg [,int $type=O] [,string $dest] [, string $extra_headers])

Выше мы рассматривали директивы log_errors и error_log, которые заставляют РНР записывать диагностические сообщения в файл, а не только выводить их в браузер. Функция error_log по своему смыслу похожа на trigger_error(), однако, она заставляет интерпретатор записать некоторый текст ($msg) в файл журнала (при нулевом $type и по умолчанию), заданный в директиве error_log. Основные значения аргумента $type, которые может принимать функция, перечислены ниже: