Любимый профессор хостинга
    
Группа: Любимый хостинг-ROOT
Сообщений: 1 143
Регистрация: 11.2.2008
Пользователь №: 887

|
PHP предоставляет прекрасную возможность контролировать возникающие ошибки. Здесь мы поговорим о том, как обработать ошибку - сообщить (или не сообщить) о происшествии пользователю, в случае необходимости - сообщить администратору с помощью электронной почты, записать информацию о происшествии в log-файл.
Итак, для начала давайте определимся, что такое ошибки в PHP.
PHP поддерживает следующие уровни ошибок:
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_ALL E_STRICT
На самом деле - это просто константы, которые используются для определения уровня обработки ошибок, построения бит-маски. Константы имеют "говорящие" имена. Глядя на константу - мы можем сказать, что ошибка уровня E_PARSE возникает в случае синтаксической ошибки, E_NOTICE - это напоминание программисту о нарушении "хорошего стиля" программирования на PHP.
Несколько примеров:
Когда соединение с базой данных MySQL (или другой) завершается неудачей - интерпретатор PHP сообщает об ошибке уровня E_WARNING
Warning: mysql_connect(): Access denied for user: 'VVingless@localhost' (Using password: YES) In /home/mysite/index.php (line 83)
Замечание: Для того чтобы интерпретатор PHP сообщал об ошибках - PHP должен быть настроен соответствующим образом: флаг display_errors должен быть включен - 1, директива error_reporting должна указывать на то, что необходимо отображать ошибки уровня E_WARNING (желательно конечно и другие). Если значения этих директив не удовлетворяют вашим требованиям - вы можете попробовать установить их самостоятельно, положив в папку со скриптом файл .htaccess (точка в начале имени обязательна) примерно такого содержания:
php_flag display_errors on php_value error_reporting "E_ALL & ~E_NOTICE"
Это означает, что сообщения об ошибках будут показываться, причем всех уровней, кроме E_NOTICE
Когда программист допускает синтаксическую ошибку - интерпретатор PHP сообщает об ошибке уровня E_PARSE
Parse error: parse error, unexpected '(', expecting T_STRING in /home/mysite/index.php on line 150
Но самые интересные для нас уровни ошибок - E_USER_ERROR и E_USER_WARNING. Как становится понятно из названия - это уровни ошибок, которые может устанавливать пользователь. Для этого существует функция trigger_error() - с её помощью, Вы можете сообщать пользователю о происшествии так, как это делает PHP.
Как известно из руководства по PHP - функция trigger_error() принимает два параметра.
void trigger_error ( string error_msg [, int error_type])
Первый параметр - текстовое сообщение об ошибке, например "файл не найден". Второй параметр - определяет уровень ошибки. Функция trigger_error() работает только с семейством ошибок E_USER - это значит, что вы можете установить ошибку уровня E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE и не можете установить ошибку уровня E_WARNING. Второй параметр является не обязательным, и по умолчанию принимает значение E_USER_NOTICE.
Давайте попробуем:
Допустим, наши данные для ленты новостей хранятся в файле news.txt, и если файл не найден - необходимо сообщить об ошибке. Текст программы будет выглядеть примерно так:
if (!file_exists('/home/mysite/news.txt')) { trigger_error('News file not found'); }
В результате интерпретатор PHP сообщит об ошибке уровня E_USER_NOTICE
Notice: News file not found in /home/mysite/index.php on line 47
Но что нам это даёт? Для начала то, что если в php.ini или файле .htaccess были установлены директивы
php_value log_errors "1" php_value log_errors_max_len "1024" php_value error_log "/home/mysite/my.log"
То в файл /home/mysite/my.log автоматически будет добавлена запись о происшествии.
[23-Mar-2004 13:52:03] PHP Notice: News file not found in /home/mysite/index.php on line 47
Далее, с помощью функции set_error_handler() мы можем установить свой собственный обработчик ошибок возникающих во время выполнения PHP скрипта.
Как известно из мануала - в PHP 4 функция принимает один единственный строковый параметр - имя функции, которая будет выполняться каждый раз, когда происходит ошибка. PHP 5 даёт возможность установить ещё один параметр - тип ошибок которые будут обрабатываться с помощью нашего обработчика. Функция возвращает строку - имя функции обработчика, который был установлен до этого момента.
string set_error_handler ( callback error_handler [, int error_types])
устанавливаем так
set_error_handler ("my_error_handler");
Пользовательская функция, которая будет обрабатывать ошибки, может принимать следующие входные параметры:
- код уровня ошибки - строковая интерпретация ошибки - имя файла, в котором произошла ошибка - строка, в которой произошла ошибка
Следует так же заметить, что эта функция не может обрабатывать ошибки уровней E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING
Это связанно с тем, что ошибки перечисленных уровней происходят до того, как интерпретатор получает информацию о пользовательском обработчике ошибок.
Итак, объявляем нашу функцию
function my_error_handler($code, $msg, $file, $line) {
}
Замечание: каждый более-менее объемный скрипт обычно разделяется на несколько файлов для удобства работы с ним. Как организовывать модульность программы - тема отдельно разговора. Сейчас же, я хочу лишь посоветовать выделять общие настройки в отдельный файл, который будет подключаться в начале программы с помощью инструкции include, либо с помощью директивы auto_prepend_file. В этот файл можно поместит и наш обработчик. Установка обработчика ошибок должна осуществится как можно ближе к началу программы, желательно в самом начале.
Для того чтобы убедится что это действительно работает - создадим новый PHP файл, и попробуем запустить его
Содержимое файла myerrortest.php
<?php
function my_error_handler($code, $msg, $file, $line) {
echo "Произошла ошибка $msg ($code)<br>\n"; echo "$file ($line)"; }
set_error_handler('my_error_handler');
if (!file_exists('/home/mysite/news.txt')) { trigger_error('News file not found'); }
?>
Результат обработки данного файла будет таким:
Произошла ошибка News file not found (1024) /home/mysite/myerrortest.php (12)
Теперь у нас есть функция, которая получает данные обо всех происходящих ошибках. Подумаем, как мы можем это использовать.
Будем обрабатывать ошибки уровней E_ERROR E_WARNING E_NOTICE E_USER_ERROR E_USER_NOTICE
Первые три ошибки в хорошей законченной программе не должны происходить вообще, поэтому о них мы будем только сообщать пользователю выводом текста ошибки на экран. Так можно работать, пока скрипт в состоянии разработки, затем сообщения о них можно либо отключить, либо записывать в log-файл.
Что касается остальных двух - как Вы уже догадались - они могу там пригодиться. Мы сами будем вызывать ошибки этих уровней в случае необходимости. Допустим - ошибки уровня E_USER_ERROR - будем вызывать в случае, когда сообщение об ошибке должно попасть в log-файл и быть отправлено на e-mail администратору (например - ошибка при выполнении SQL запроса, или отсутствии парв доступа к необходимому файлу). Ошибки уровня E_USER_NOTICE будут вызываться при возникновении "лёгких" ошибок (например - пользователь некорректно заполнил форму, или запросил из базы несуществующую запись).
Теперь наша функция обработки ошибок будет выглядеть примерно так:
// Немного предварительных настроек
// устанавливаем режим отображения ошибок // отображать все ошибки, кроме E_NOTICE error_reporting (E_ALL & ~E_NOTICE);
// эта константа отвечает за // включение/выключение режима отладки // во время отладки - сообщения не отсылаются // по почте, а просто печатаются на экран define('DEBUG', 0);
// это глобальная переменная, в которой // будет храниться сообщение, которое // должен видеть пользователь $MSG = '';
// e-mail разработчика, куда отправлять ошибки define('ADM_EMAIL','admin@example.com');
// log-файл define('LOGFILE','/home/mysite/mylog.log');
// разница во времени с сервером (в секундах) define('TIMEOFFSET', 0);
// сама функция
function my_error_handler($code, $msg, $file, $line) { // глобальная переменная, в которую будет // записываться сообщение об ошибке. global $MSG;
// пропускаем ошибки уровня E_NOTICE // и игнорируем ошибки, если режим сообщения об ошибках отключен if ( ($code == E_NOTICE) or (error_reporting() == 0) ) { return; }
// если мы вызвали ошибку уровня E_USER_NOTICE - просто // записать текст ошибки в глобальную переменную $MSG // и прекратить выполнение функции
if ($code == E_USER_NOTICE) { $MSG = $msg; Return; }
// если ошибка уровня E_ERROR - печатаем текст ошибки // и завершаем выполнение скрипта
if ($code == E_ERROR) { die ('<br><b>ERROR:</b> '.$msg.'<br>In '.$file.' (line '.$line.')<br>'); }
// если ошибка уровня E_WARNING - печатаем текст ошибки // и прекращаем выполнение функции
if ($code == E_WARNING) { echo '<br><b>WARNING:</b> '.$msg.'<br>In '.$file.' (line '.$line.')<br>'; Return; }
// если ошибка уровня E_USER_ERROR
if ($code == E_USER_ERROR) {
// записываем в переменную $MSG текст, о том что произошла ошибка, // причины сообщать не будем, только сообщим что подробности // отправлены на e-mail кому следует.
$MSG = 'Критическая Ошибка: действие выполнено небыло. <br> Сообщение об ошибке было отправлено разработчику.';
// подробности записываем в переменную $text
$text = $msg.'<br>'.'Файл: '.$file.' ('.$line.')';
// Если константа DEBUG установлена в 1 - печатаем информацию об // ошибке на экран, если нет - отправляем текст ошибки почтой // функция error_mail() и пишем в log - функция error_writelog()
if (DEBUG == 1) { error_print($text); } else { error_mail($text); error_writelog($text); }
Return; } }
// устанавливаем обработчик set_error_handler('my_error_handler');
Теперь описываем служебные функции
// ф-я печатает ошибку на экран function error_print($text) { echo $text.'<p>'; }
// ф-я отправляет ошибку почтой function error_mail($text) { $text = str_replace("<br>", "\n", $text);
$info = 'Время: '.get_datetime()."\nRemote IP:".get_ip()."\n";
mail(ADM_EMAIL, "Error reporting", $info.$text); }
// ф-я пишет ошибку в лог function error_writelog($text) { $text = str_replace("<br>", "\t", $text); if (@$fh = fopen(LOGFILE, "a+")) { fputs($fh, get_datetime()."\t".get_ip()."\t".$text."\n"); fclose($fh); } }
// получаем время, с учётом разницы во времени function get_time() { return(date("H:i", time () + TIMEOFFSET)); }
// получаем дату, с учётом разницы во времени function get_date() { return(date("Y-m-d", time () + TIMEOFFSET)); }
// получаем дату и время, с учётом разницы во времени function get_datetime() { return get_date().' '.get_time(); }
// получаем IP function get_ip() { return($_SERVER['REMOTE_ADDR']); }
И наконец пример использования
// ф-я записывает новость в файл function write_news($title, $text) { $news_file = '/home/mysite/news.txt';
// проверяем наличие заголовка - ошибка не критическая if (!trim($title)) {
// для того чтобы определить что функция завершилась // неудачей - необходимо вернуть false. Функция // trigger_error() - возвращает true, мы будем // возвращать её инвертированный результат
return !trigger_error('Необходимо указать заголовок новости'); }
// проверяем наличие текста новости - ошибка не критическая if (!trim($text)) { return !trigger_error('Необходимо указать текст новости'); }
// проверяем наличие файла в который будем писать // если файл не найден - возникает критическая ошибка
if (!file_exists($news_file)) { return !trigger_error('Файл базы новостей не найден!', E_USER_ERROR); }
// ...тут предварительная обработка данных...
// записываем новость $fh = fopen($news_file, "a+"); fputs($fh, $title."\t".$text."\n"); fclose($fh);
// если всё нормально - функция возвращает true return true; }
// пытаемся записать новость // эти данные могут приходить из web-формы
$res = write_news("Моя новость", "Текст моей новости");
if ($res === false) {
// если вернулся false - печатаем ошибку echo $MSG;
} else {
// если всё в порядке - можно сообщить об этом // а лучше отфорвардить пользователя куда-нибудь. echo 'Новость была добавлена'; }
Для того чтобы пример заработал - просто скопируйте в PHP-файл три предыдущих блока кода. Не забудьте установить права доступа на log-файл 777 для того чтобы скрипт мог с ним работать, прописать правильные пути и указать свой e-mail. Вы можете включить режим отладки установкой переменной DEBUG в 1.
Это довольно простой пример, тему можно развивать.
|