В случае возникновения серьезной ошибки программа завершает работу по exit() или die().
Такой метод неприемлем в коде библиотек общего пользования: ведь вызывающая программа может не ожидать, что ее выполнение может быть прервано из-за какой-нибудь мелочи вроде невозможности открытия файла журнала.
Практически все стандартные функции РНР в случае возникновения ошибочной ситуации возвращают false или NULL, а также вызывают trigger_error() для фиксирования диагностического сообщения (его можно потом перехватить при помощи функции-обработчика). Например, функция fopen() при невозможности открытия файла возвращает false, и мы в дальнейшем должны проверить результат на "истинность".
Этот метод имеет три недостатка.
● Главный недостаток — программист может забыть проверить возвращенное функцией значение, и продолжить выполнение программы так, будто бы ничего не произошло. В большинстве случаев это породит лавинообразное нарастание количества диагностических сообщений, не имеющих никакого отношения к сути проблемы.
● Любое значение, возвращаемое функцией, может быть по смыслу допустимым. Например, стандартная функция unserialize() распаковывает некоторую переменную из ее строкового представления (сгенерированного вызовом serialize()) и возвращает ее исходное значение. Что должна вернуть функция в случае ошибки? Если, например, NULL, то где гарантия, что действительно произошла ошибка, а исходная переменная не содержала просто значение NULL до упаковки?
● Код восстановления приходится постоянно дублировать. Например, если в программе нужно открыть 10 разных файлов, мы будем вынуждены 10 раз проверить возвращаемое функцией fopen() значение. Легко представить, как сильно это "раздует" программу. Еще хуже вы себя почувствуете, если вспомните, что в будущем может понадобиться модифицировать код восстановления: придется делать это в 10 местах.
Часто одного лишь недопустимого возвращаемого значения оказывается недостаточно для хранения всей информации. Поэтому в дополнение к предыдущему способу в РНР иногда применяется запись информации об ошибке в некоторую внутреннюю переменную, которую можно будет в дальнейшем считать и проанализировать другими функциями.
Так работают, например, инструменты для доступа к MySQL в PHP: mysql_connect(), mysql_query() и т. д. Вы можете проверить, не вернули ли функции "ложное" значение, а потом использовать mysql_error() или mysql_errno() для получения дополнительной информации. Состояние программы, в котором когда-то ранее произошла ошибка, требующая дополнительного анализа, называют ненормальным. Главный недостаток ненормального состояния в том, что если вдруг произойдет еще одна ошибка, то информация о ней "затрет" предыдущее сообщение. Таким образом, мы вынуждены периодически проверять, не находится ли программа в ненормальном состоянии, и предпринимать дополнительные действия. Это сильно увеличивает код, кроме того, программист может забыть вставить в программу требуемые проверки.
Этот способ применяют к обработке несерьезных ошибок. Для перехвата серьезных ошибочных ситуаций он применим мало. Действительно, функция-обработчик имеет в своем распоряжении те же самые три альтернативы. Она не получает в свое распоряжение ни текущие локальные переменные программы, ни информацию о том, что следует делать в каждом персональном случае.
Уровнем детализации сообщений об ошибках, а также другими параметрами управляют директивы РНР, перечисленные ниже.
error_reporting
● Возможные значения: числовая константа (по умолчанию — E_ALL~E_NOTICE.
● Где устанавливается: php.ini, .htaccess, ini_set ().
Устанавливает "уровень строгости" для системы контроля ошибок РНР. Значение этого параметра должно быть целым числом, которое интерпретируется как десятичное представление двоичной битовой маски. Установленные в 1 биты задают, насколько детальным должен быть контроль. Можно также не возиться с битами, а использовать константы. В табл. 1.1 приведены некоторые константы, которые на практике применяются чаще всего.
Таблица 1.1. Биты, управляющие контролем ошибок
Бит | Константа PHP | Назначение |
1 | E_ERROR | Фатальные ошибки |
2 | E_WARNING | Общие предупреждения |
4 | E_PARSE | Ошибки трансляции |
8 | E_NOTICE | Предупреждения |
16 | E_CORRE_ ERROR | Глобальные предупреждения (почти не используется) |
32 | E_CORRE_STRING | Глобальные ошибки (не используется) |
2048 | E_STRICT | Различные "рекомендации" РНР по улучшению кода (например, замечания насчет вызова устаревших функций) |
2047 | E_ALL | Все перечисленные флаги, за исключением E_STRICT |
Чаще всего встречается значение 7 (1+2 + 4), или, что то же самое, E_ALL~E_NOTICE (здесь оператор ~ означает побитовое "исключающее ИЛИ"). Оно задает полный контроль, кроме некритичных предупреждений интерпретатора (таких, например, как обращение к неинициализированной переменной). Часто это значение задается по умолчанию при установке РНР.
Если вы разрабатываете скрипты на РНР, первое, что вам стоит сделать, — это устанавливать значение error_reporting равным E_ALLили даже E_ALL|E_STRICT, т.е. включить абсолютно все сообщения об ошибках.
Хотя в уже имеющихся сценариях (включая популярные системы phpBB, phpNuke и т. д.) это, скорее всего, породит целые легионы самых разнообразных предупреждений, не стоит их пугаться: они свидетельствуют лишь о недостаточно хорошем качестве кода.
Режим E_ALL очень помогает при отладке скриптов. Существуют распространенные ошибки, над которыми можно просидеть не один час, в то время как с включенным режимом E_ALL они обнаруживаются в течение нескольких минут.
Лучше всего держать в файле php.ini максимально возможный режим контроля ошибок —E_ALLили даже E_ALLE_STRICT, т.е. включить абсолютно все сообщения об ошибках, а в случае крайней необходимости отключать его в скриптах в персональном порядке. Для этого существует три способа.
Способы отключения режима контроля ошибок:
● использоватьфункциюerror_reporting(E_ALL~E_NOTICE);
● запуститьфункциюini_set("error_reporting", E_ALL~E_NOTICE);
● использовать оператор @ для локального отключения ошибок.
display_errors
log_errors
● Возможныезначения: on или off.
● Гдеустанавливается: php.ini, .htaccess, iniseto.
Если директива display_errors установлена в значение on, все сообщения об ошибках и предупреждения выводятся в браузер пользователя, запустившего скрипт.
Если же установлен параметр log_errors, то сообщения дополнительно попадают и в файл журнала (см. ниже директиву error_log).
При отладке скриптов рекомендуется устанавливать display_errors в значение on, потому что это сильно упрощает работу. И наоборот, если скрипт работает на хостинге, сообщения об ошибках нежелательны — лучше их выключить, а вместо этого включить log_errors.
error_log
● Возможные значения: абсолютный путь к файлу (по умолчанию — не задан).
● Где устанавливается: php.ini, .htaccess, ini_set().
В PHP существуют два метода вывода сообщений об ошибках: печать ошибок в браузер и запись их в файл журнала (log-файл). Директива error_log задает путь к журналу.
Для установки режима вывода ошибок во время работы программы служит функция error_reporting().
int error_reporting([int $level])
Эта функция устанавливает "уровень строгости" для системы контроля ошибок РНР, т. е. величину параметра error_reporting в конфигурации РНР.
Рекомендуется первой строкой сценария ставить вызов:
error_reporting(E_ALL);
При этом могут раздражать "мелкие" сообщения типа "использование неинициализированной переменной". Практика показывает, что эти предупреждения свидетельствуют (чаще всего) о возможной логической ошибке в программе, и что при их отключении может возникнуть ситуация, когда программу будет очень трудно отладить.
Есть и еще один аргумент в пользу того, чтобы всегда включать полный контроль ошибок. Это — существование в РНР оператора @. Если этот оператор поставить перед любым выражением, то все ошибки, которые там возникнут, будут проигнорированы.
Если в выражении используется результат работы функции, из которой вызывается другая функция и т. д., то предупреждения будут заблокированы для каждой из них. Поэтому осторожно применяйте @.
Например:
if (!@filemtime("notextst.txt") )
echo "Файл не существует!";
Попробуйте убрать оператор @ — тут же получите сообщение: "Файл не найден", а только после этого — вывод оператора echo. Однако с оператором @ предупреждение будет подавлено, что и требовалось.
В приведенном примере, возможно, несколько логичнее было бы воспользоваться функцией file_exists(), которая как раз и предназначена для определения факта существования файла, но в некоторых ситуациях это не подойдет. Например:
//Обновить файл, если он не существует или очень старый
if ( ! file_exists($fname) || filemtime ($fname) <time () -60*60)
myFunctionForUpdateFile($fname);
Сравните со следующим фрагментом:
// Обновить файл, если он не существует или очень старый
if (@filemtime($fname)<time()-60*60)
myFunctionForUpdateFile($fname);
Всегда помните об операторе @. Он удобен. Рекомендуется не рисковать, задавая слабый контроль ошибок при помощи функции error_reporting(), если такой контроль и так можно локально установить при помощи оператора @?
Оператор отключения ошибок @ ценен еще и тем, что он блокирует не только вывод ошибок в браузер, но также и в log-файл. Пример из листинга 1.1 иллюстрирует ситуацию.
Листинг 1.1. Файл er.php