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 автоматически будет добавлена запись о происшествии.
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
Результат обработки данного файла будет таким:
Произошла ошибка 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","[email protected]");
// 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 ("
ERROR: ".$msg."
In ".$file." (line ".$line.")
");
}
// если ошибка уровня E_WARNING - печатаем текст ошибки
// и прекращаем выполнение функции
if ($code == E_WARNING) {
echo "
WARNING: ".$msg."
In ".$file." (line ".$line.")
";
Return;
}
// если ошибка уровня E_USER_ERROR
if ($code == E_USER_ERROR) {
// записываем в переменную $MSG текст, о том что произошла ошибка,
// причины сообщать не будем, только сообщим что подробности
// отправлены на e-mail кому следует.
$MSG = "Критическая Ошибка: действие выполнено небыло.
Сообщение об ошибке было отправлено разработчику.";
// подробности записываем в переменную $text
$text = $msg."
"."Файл: ".$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."
";
}
// ф-я отправляет ошибку почтой
function error_mail($text)
{
$text = str_replace("
", "n", $text);
$info = "Время: ".get_datetime()."nRemote IP:".get_ip()."n";
mail(ADM_EMAIL, "Error reporting", $info.$text);
}
// ф-я пишет ошибку в лог
function error_writelog($text)
{
$text = str_replace("
", "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.
- print , echo и другие функции, производящие вывод
- Необработанные разделы, предшествующие
Если источник ошибки упоминается как за закрытие?> то здесь выписывается какой-то пропущенный или необработанный текст. Маркер конца PHP не завершает выполнение script при этом точка. Любые символы текста/пробела после того, как они будут выписаны как содержимое страницы до сих пор.
Общепринято, в частности, новичкам, что trailing ?> PHP теги close должны быть опущены. Это предотвращает небольшую часть этих случаев. (Чаще всего сценарии include()d являются виновниками.)
- Источник ошибки, указанный как "Неизвестно в строке 0"
Обычно это расширение PHP или php.ini, если источник ошибок конкретизируется.
- Иногда параметр настройки потока gzip или ob_gzhandler .
- Но это может быть и любой загруженный в два раза модуль extension= генерирование неявного сообщения о запуске/предупреждении PHP.
- Предыдущие сообщения об ошибках
Если другой оператор или выражение PHP вызывает предупреждение или уведомление распечатывается, что также считается преждевременным выходом.
В этом случае вам нужно избежать ошибки, задержать выполнение оператора или подавить сообщение, например. isset() или @() - когда либо не препятствует отладке позже.
- Значения байтов байтов в начале php файла. Изучите ваши php файлы с помощью шестнадцатеричного редактора, чтобы узнать, является ли этот случай. Они должны начинаться с байтов 3F 3C . Вы можете безопасно удалить спецификацию EF BB BF с начала файлов.
- Явный вывод, например вызовы echo , printf , readfile , passthru , код до
DO CHECK FOR BLANK SPACES HERE AS WELL; THIS LINE (blank line) SHOULD NOT EXIST.
Большая часть времени это должно решить ваш problem.Do проверить все файлы, связанные с файлом вам require .
Примечание. Иногда EDITOR (IDE), например gedit (стандартный Linux-редактор), добавляет одну пустую строку в файл save save. Этого не должно быть. Если вы используете linux. вы можете использовать редактор VI для удаления пробела/строк после?> в конце страницы.
Если это не ваше дело, тогда вы можете использовать ob_start для буферизации вывода, как показано ниже:
Возможное решение 2:
Это приведет к буферизации вывода, и ваши заголовки будут созданы после буферизации страницы.
Вместо строки ниже
//header("Location:".ADMIN_URL."/index.php");
написать
Echo("location.href = "".ADMIN_URL."/index.php?msg=$msg";");
?>
Преднамеренное:
Если у вас error_reporting или display_errors отключено на php.ini , то предупреждение не появится. Но игнорирование ошибок не вызовет проблемы далеко. Заголовки по-прежнему не могут быть отправлены после преждевременного выхода.
Поэтому, когда header("Location: ...") перенаправляет молча, рекомендуется проконтролировать предупреждения. Обозначить их двумя простыми командами на вызов script:
Error_reporting(E_ALL); ini_set("display_errors", 1);
Или set_error_handler("var_dump"); , если все остальное не работает.
Говоря о переадресации заголовков, вы должны часто использовать идиому вроде это для конечных путей кода:
Exit(header("Location: /finished.html"));
Предпочтительно даже функция полезности, которая печатает сообщение пользователя в случае сбоев header() .
Буферизация вывода в качестве обходного путиОн может скрывать пробелы для вывода HTML. Но как только приложение логические попытки отправить двоичный контент (например, сгенерированное изображение), буферизованный посторонний выход становится проблемой. (Необходимость ob_clean() как предыдущий вариант.)
Буфер ограничен по размеру и может быть легко переполнен, если оставить его по умолчанию. И это не редкость, трудно отследить когда это произойдет.
Оба подхода могут стать ненадежными - в частности, при переключении между разработки и/или производственные серверы. Именно поэтому буферизация вывода широко считается просто костылем/строго обходным путем.
Это сообщение об ошибке запускается, когда что-либо отправляется перед отправкой заголовков HTTP (с setcookie или header). Общие причины вывода чего-либо перед заголовками HTTP:
Случайные пробелы, часто в начале или в конце файлов, например:
Чтобы избежать этого, просто оставьте закрытие?>