WebScript.Ru
C:\   главная  ::   о сайте  ::  каталог скриптов  ::  гнездо  ::  форум  ::   авторам  :: Новостройки ::   ХОСТИНГ  ::

|| разделы::
|| поиск по сайту::

|| реклама::
|| новости почтой::
Рассылки Subscribe.Ru ::



Новости сайта WebScript.Ru
Популярные статьи

Hot 5 Stories

|| рекомендуем::




Обработка ошибок в PHP


Прислал: Павел Пушкарёв [ 12.04.2004 @ 11:19 ]
Раздел:: [ Статьи по PHP ]


Обработка ошибок с помощью trigger_error() и set_error_handler()

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.

Это довольно простой пример, тему можно развивать.

Ссылки по теме:

//www.php.net/manual/ru - руководство по PHP
//www.php.net/manual/ru/ref.errorfunc.php - обработка ошибок
//www.php.net/manual/ru/function.set-error-handler.php - функция set_error_handler
//www.php.net/manual/ru/function.trigger-error.php - функция trigger_error

Павел Пушкарёв


 :::::  RomikChef пишет 12.04.2004 @ 13:06 
Жаль, что автор совсем не умеет писать статьи.
Тема обработки ошибок - важная и нужная.
Однако раскрыта она очень коряво.
Есть код - весьма неочевидный - и к нему за уши притянуто некое подобие объяснения. Притянуто неудачно. Текст рваный, нет четкой связи между кусками, автор скачет с пятого на десятое, непонятно, что он хотел вообще сказать.

Зачем написан этот код? Чем он лучше других? Нет ответа.

Автор задает вопрос: что такое ошибки в пхп?. И отвечает - это... КОНСТАНТЫ!

Автор рассказывает, как сделать так, чтобы пхп о пользовательских ошибках сообщал, как о соих собственных - с указанием имени файла и строки. Автор задумывался когда-нибудь - ЗАЧЕМ это делать?

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

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

Порочна сама идея сваливания всех ошибок в кучу.
Ошибки пользователя при взаполнении формы - это не ошибки! Надо понимать, что это ШТАТНЫЙ режим работы программы. И использовать для них тот же обраобтчик, что и для ошибок скрипта - неправильно.

Автору надо понять, что не стоит лезть вообще в механизм робработки ошибок. Пускай пхп ведет себе лог. Свой обработчик сделать разве что, чтобы на мейл ошибки отправлялись. Но не приплетать к ним обработку действий пользователя!

плюс ко всему - весьма грязный и малореальный код. строк 20 на работу с датой. Аж две специальные функции. плюс - отдельная функция для получения айпи адреса.
Обработка ошибок пользовательского воода - то, ради чего все и затевалось - пролучилась ужасной. Данный пример сообщает пользователю только ПО ОДНОЙ ОШИБКЕ ЗА РАЗ!
Это садизм, вообще-то. Честное слово - любой начинающий, но способный пхпшник обрабатывает формы дружественнее для пользователя, причем без переопределения стандартного обработчика.

Вывод.Полезная идея, но реализована так криво, что становится непонятным - зачем все эти телодвижения. Все использованные примеры можно реализовать гораздо проще, встроенными средствами языка, без написания собственного обработчика.

Жаль. я так надеялся, то статья интересная - тема-то важная. Но, увы - интересность темы не гарантирует актуальность статьи.


 :::::  Пашка пишет 12.04.2004 @ 22:03 
Ромик негодяй, не угодишь ему :)

Да, немного кривовато получилось :/

Тем не менее – если бы такая статья попалась мне когда я начинал работать с PHP – я был бы рад :)

По поводу «Ошибки – это константы» Ну, не ясно выразился, бывает.

Пример использования, опять таки – очень упрощённый – чтобы было легче понять как это работает.

Вообще – там много чего можно было бы ещё написать, но всё это уже «вторая серия».

Суть статьи – введение в обработку ошибок, использование обработчиков, то есть «что с этим вообще можно сделать»


 :::::  RomikChef пишет 14.04.2004 @ 12:33 
Ты не первый, кто пишет в свое оправдание, что "если бы мне попалась статья...".
И потом пишет банальности.
Чуть ниже тут "статья" про постраничный вывод. Не умеем читать - будем писать.

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


 :::::  Прохожий пишет 14.05.2004 @ 23:19 
Ромик, а сам-то можешь по этому поводу что-нибудь дельное сказать? Или только какашками в других кидаться умеем?
 :::::  Миха пишет 07.06.2004 @ 12:30 
Надо писать скрипты без ошибок и косяков не будет :))
 :::::  гость пишет 25.07.2004 @ 05:40 
вместо

if (!file_exists($news_file)) {
return !trigger_error('Файл базы новостей не найден!', E_USER_ERROR);
}

лучше писать

assert('file_exists(\'$news_file\')');

и обрабатывать соответственно через assert_options()
 :::::  noob пишет 13.01.2005 @ 17:41 
а это вот все - будет отлавливать критикал ерор?
 :::::   пишет 30.01.2005 @ 16:42 
А как на счёт 'Fatal Error'?
 :::::  lamer пишет 15.03.2008 @ 22:37 
хорошая статья!
 :::::  Автор пишет 15.03.2008 @ 22:47 
Статья безбожно устарела. Используйте Exceptions.
Имя:
Email:
URL

Введите сумму двух чисел девять и одинн (девять+одинн=?)
Запомнить мою информацию

* Html запрещен* Ваш E-mail опубликован не будет.

Copyright © 2000-2001 WebScript.Ru nas@webscript.ru
Design © 2001 by Parallax Design Studio (aka Spectator.ru)
Все торговые марки и авторские права на эту страницу принадлежат их соответствующим владельцам.
Сгенерировано за: 0.0472639