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

Как обнаружить ошибку PHP на сайте
1. Встроенными средствами браузера
Итак, если на сайте вместо привычной страницы ничего не отображается (вы видите “пустую страницу”), то, вероятнее всего, в одном из скриптов возникла ошибка. В этом можно убедиться, воспользовавшись встроенными «Инструментами разработчика» вашего браузера. В каждом браузере они могут называться немного по-разному, но суть от этого не меняется.
Например, в браузере Google Chrome это вкладка Dev Tools (или «Инструменты разработчика»). В Mozilla Firefox — это расширение Firebug (его нужно установить отдельно в меню Adds On) или же вкладка Developer.
Внутри «Инструментов разработчика» нас интересует вкладка, которая называется Network (или Net, или каким-то похожим образом).
Если на странице сайта присутствует ошибка, в этой вкладке вы увидите код ответа 500 (“Internal Server Error”).
2. Если вывод сообщений об ошибках в браузер отключен
Случается, что вывод сообщений об ошибках в браузер отключён. Чтобы сообщение об ошибке отображалось в браузере, достаточно добавить в файл .htaccess в корневой директории сайта следующую строку:
php_value display_errors on
Файл .htaccess вы найдете по адресу: /home/login/domains/domain.ru/public_html/, где вместо login следует подставить логин вашего аккаунта, а вместо domain.ru — домен вашего сайта.
После сохранения файла .htaccess и обновления страницы вы сможете увидеть ошибку.
Если сайтом используется, например, CMS WordPress, то отображение ошибок можно также включить, заменив в файле wp-config.php:
define(‘WP_DEBUG’, false);
на:
define(‘WP_DEBUG’, true);
3. С помощью журнала ошибок PHP
Иногда по различным причинам отображать ошибки в браузере нежелательно. В этом случае лучше всего сохранять их в какой-нибудь файл, например errors.log — журнал ошибок PHP. Для этого достаточно в файле .htaccess добавить следующую строку:
php_value error_log /home/login/domains/domain.ru/log/errors.log
Здесь /home/login/domains/domain.ru/log/errors.log — это полный путь до файла, в который будут записываться ошибки PHP (если файла с таким именем нет, он будет создан автоматически при появлении ошибки).
Теперь, если мы снова зайдем на сайт с ошибкой (либо обновим страницу с ошибкой), то в errors.log будут записаны сообщения об ошибках.
Журнал ошибок PHP можно просмотреть, например, с помощью файлового менеджера в Панели управления, открыв файл errors.log:

Также можно открыть файл с ошибками и нажать кнопку “Включить автообновление”. Таким образом, новые записи в журнале можно просматривать в реальном времени.
Расшифровка ошибок PHP
Как правило, в сообщении об ошибке достаточно подробно указано где именно и при выполнении какой части кода она возникла. Например:
Здесь ошибка заключается в следующем:
Fatal error: Call to undefined function weblizar_get_options() in /home/login/domains/domain.ru/public_html/wp-content/themes/enigma/header.php on line 14
“Вызов неопределенной функции weblizar_get_options() в файле используемой на сайте темы enigma”.
Вероятнее всего, был поврежден один из файлов темы, поэтому можно восстановить только директорию темы ./wp-content/themes/enigma/ , а не всего сайта.
Что делать, в зависимости от типа ошибки PHP
Условно ошибки PHP можно разбить на 4 уровня:
- PARSE ERROR
- FATAL ERROR
- WARNING
- NOTICE
Parse Error
Возникают, если уже на этапе проверки кода интерпретатором PHP найдена ошибка. Чаще всего это синтаксические ошибка (например, пропущенная точка с запятой). Скорее всего, такая ошибка возникла в результате последних внесенных на сайт изменений.
Что делать?
1. Если вы НЕ специалист в PHP, восстановите сайт из последней резервной копии на тот момент, когда сайт работал без ошибок.
2. Если вы специалист и самостоятельно вносили правки в код сайта, вы наверняка сможете отследить синтаксическую ошибку и исправить ее. Но проще все же воспользоваться пунктом 1.
Fatal Error и Warning
Возникают, если при выполнении кода какой-то его участок не может быть выполнен (например, попытка открыть несуществующий файл). Разница между 2-ым и 3-им уровнем в том, что при получении “критической ошибки” (FATAL ERROR) выполнение скрипта завершится, а при получении “предупреждения” (WARNING) — нет.
Что делать?
Восстановите сайт из последней доступной резервной копии на тот момент, когда он работал без ошибок.
Notice
К этому уровню ошибок относятся различные “замечания”, суть которых обычно отображена в тексте ошибки.
Что делать?
Если замечание самостоятельно исправить не получается, обратитесь в службу поддержки или же восстановите сайт из последней доступной резервной копии на тот момент, когда он работал без ошибок.
Частые ошибки PHP и их решение
Fatal Error: Allowed Memory
Означает, что для выполнения какой-либо части кода PHP не хватает выделенной оперативной памяти. При этом лимит памяти ограничен какими-то директивами «изнутри» сайта (то есть где-либо в скриптах сайта, либо директивой memory_limit в файле .htaccess). Чтобы исправить это, измените данный лимит в большую сторону, например, в файле .htaccess.
Для этого найдите в .htaccess такую директиву:
php_value memory_limit 128M
Вместо 128M укажите желаемый размер ограничения. Обратите внимание, что символ «M» (латинская M) указывается слитно со значением.
Помните, что есть максимальные значения памяти, отведенной на выполнение скриптов PHP, предусмотенные вашим тарифом хостинга (например, на тарифах виртуального хостинга это 512 Мб, премиум — 1024 Мб). Уточните эти значения у вашего провайдера, если они не указаны явно.
Fatal Error: Out of memory
То же самое, что и предыдущая ошибка, с той разницей, что достигнут лимит памяти, заданный “снаружи”. Обратите внимание на параметр “Памяти на процесс, Мб, не более“ в условиях пользования нашим сервисом.
Для решения вопроса в данном случае, скорее всего, потребуется либо оптимизация скриптов, чтобы они потребляли меньше памяти, либо разбиение процессов на части. Например, объемную загрузку или выгрузку данных, если она упирается в данный лимит, имеет смысл производить частями.
Также в этом случае мы советуем попробовать отключить акселераторы PHP, если они у вас подключены.
Unable to allocate memory for pool
Сайтам на аккаунте не хватает выделенной на тарифном плане памяти для акселераторов PHP.
Для решения проблемы вы можете отключить использование акселераторов в Панели управления хостингом, в разделе «Управление персональным веб-сервером».
Также, например, можно отключить акселератор APC для определенного сайта, добавив в файл .htaccess корневой директории следующую директиву:
php_value apc.cache_by_default off
Обычно имеет смысл оставлять APC для использования на самом посещаемом из ваших сайтов — это позволит использовать именно на нем преимущества акселератора, не заполняя его память данными с других, менее посещаемых сайтов.
В случаях, когда акселератор объективно необходим для корректной и комфортной работы сайтов и его отключение нежелательно, напишите в службу поддержки.
Мы постараемся предложить возможные варианты решения.
Желаем вам приятной работы!
Время на прочтение
6 мин
Количество просмотров 66K
В статье представлена очередная попытка разобраться с ошибками, которые могут встретиться на вашем пути php-разработчика, их возможная классификация, примеры их возникновения, влияние ошибок на ответ клиенту, а также инструкции по написанию своего обработчика ошибок.
Статья разбита на четыре раздела:
- Классификация ошибок.
- Пример, демонстрирующий различные виды ошибок и его поведение при различных настройках.
- Написание собственного обработчика ошибок.
- Полезные ссылки.
Классификация ошибок
Все ошибки, условно, можно разбить на категории по нескольким критериям.
Фатальность:
- Фатальные
Неустранимые ошибки. Работа скрипта прекращается.
E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR. - Не фатальные
Устранимые ошибки. Работа скрипта не прекращается.
E_WARNING, E_NOTICE, E_CORE_WARNING, E_COMPILE_WARNING, E_USER_WARNING, E_USER_NOTICE, E_STRICT, E_DEPRECATED, E_USER_DEPRECATED. - Смешанные
Фатальные, но только, если не обработаны функцией, определенной пользователем в set_error_handler().
E_USER_ERROR, E_RECOVERABLE_ERROR.
Возможность перехвата ошибки функцией, определенной в set_error_handler():
- Перехватываемые (не фатальные и смешанные)
E_USER_ERROR, E_RECOVERABLE_ERROR, E_WARNING, E_NOTICE, E_USER_WARNING, E_USER_NOTICE, E_STRICT, E_DEPRECATED, E_USER_DEPRECATED. - Не перехватываемые (фатальные)
E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING.
Инициатор:
- Инициированы пользователем
E_USER_ERROR, E_USER_WARNING, E_USER_NOTICE. - Инициированы PHP
E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR, E_WARNING, E_NOTICE, E_CORE_WARNING, E_COMPILE_WARNING, E_STRICT, E_DEPRECATED, E_USER_DEPRECATED, E_USER_ERROR, E_RECOVERABLE_ERROR.
Для нас, в рамках данной статьи, наиболее интересны классификации по первым двум критериям, о чем будет рассказано далее.
Примеры возникновения ошибок
Листинг index.php
<?php
// определеяем уровень протоколирования ошибок
error_reporting(E_ALL | E_STRICT);
// определяем режим вывода ошибок
ini_set('display_errors', 'On');
// подключаем файл с ошибками
require 'errors.php';
Листинг errors.php
<?php
echo "Файл с ошибками. Начало<br>";
/*
* перехватываемые ошибки (ловятся функцией set_error_handler())
*/
// NONFATAL - E_NOTICE
// echo $undefined_var;
// NONFATAL - E_WARNING
// array_key_exists('key', NULL);
// NONFATAL - E_DEPRECATED
split('[/.-]', "12/21/2012"); // split() deprecated начиная с php 5.3.0
// NONFATAL - E_STRICT
// class c {function f(){}} c::f();
// NONFATAL - E_USER_DEPRECATED
// trigger_error("E_USER_DEPRECATED", E_USER_DEPRECATED);
// NONFATAL - E_USER_WARNING
// trigger_error("E_USER_WARNING", E_USER_WARNING);
// NONFATAL - E_USER_NOTICE
// trigger_error("E_USER_NOTICE", E_USER_NOTICE);
// FATAL, если не обработана функцией set_error_handler - E_RECOVERABLE_ERROR
// class b {function f(int $a){}} $b = new b; $b->f(NULL);
// FATAL, если не обработана функцией set_error_handler - E_USER_ERROR
// trigger_error("E_USER_ERROR", E_USER_ERROR);
/*
* неперехватываемые (не ловятся функцией set_error_handler())
*/
// FATAL - E_ERROR
// undefined_function();
// FATAL - E_PARSE
// parse_error
// FATAL - E_COMPILE_ERROR
// $var[];
echo "Файл с ошибками. Конец<br>";
Примечание: для полной работоспособности скрипта необходим PHP версии не ниже 5.3.0.
В файле errors.php представлены выражения, инициирующие практически все возможные ошибки. Исключение составили: E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_WARNING, генерируемые ядром Zend. В теории, встретить их в реальной работе вы не должны.
В следующей таблице приведены варианты поведения этого скрипта в различных условиях (в зависимости от значений директив display_errors и error_reporting):
| Группа ошибок | Значения директив* | Статус ответа сервера | Ответ клиенту** |
|---|---|---|---|
| E_PARSE, E_COMPILE_ERROR*** | display_errors = off error_reporting = ANY |
500 | Пустое значение |
| display_errors = on error_reporting = ANY |
200 | Сообщение об ошибке | |
| E_USER_ERROR, E_ERROR, E_RECOVERABLE_ERROR | display_errors = off error_reporting = ANY |
500 | Вывод скрипта до ошибки |
| display_errors = on error_reporting = ANY |
200 | Сообщение об ошибке и вывод скрипта до ошибки | |
| Не фатальные ошибки | display_errors = off error_reporting = ANY и display_errors = on error_reporting = 0 |
200 | Весь вывод скрипта |
| display_errors = on error_reporting = E_ALL | E_STRICT |
200 | Сообщение об ошибке и весь вывод скрипта |
* Значение ANY означает E_ALL | E_STRICT или 0.
** Ответ клиенту может отличаться от ответов на реальных скриптах. Например, вывод какой-либо информации до включения файла errors.php, будет фигурировать во всех рассмотренных случаях.
*** Если в файле errors.php заменить пример для ошибки E_COMPILE_ERROR на require "missing_file.php";, то ошибка попадет во вторую группу.
Значение, приведенной выше, таблицы можно описать следующим образом:
- Наличие в файле скрипта ошибки, приводящей его в «негодное» состояние (невозможность корректно обработать), на выходе даст пустое значение или же только само сообщение об ошибке, в зависимости от значения директивы display_errors.
- Скрипт в файле с фатальной ошибкой, не относящейся к первому пункту, будет выполняться в штатном режиме до самой ошибки.
- Наличие в файле фатальной ошибки при display_errors = Off обозначит 500 статус ответа.
- Не фатальные ошибки, как и следовало ожидать, в контексте возможности исполнения скрипта в целом, на работоспособность не повлияют.
Собственный обработчик ошибок
Для написания собственного обработчика ошибок необходимо знать, что:
- для получения информации о последней произошедшей ошибке существует функция error_get_last();
- для определения собственного обработчика ошибок существует функция set_error_handler(), но фатальные ошибки нельзя «перехватить» этой функцией;
- используя register_shutdown_function(), можно зарегистрировать свою функцию, выполняемую по завершении работы скрипта, и в ней, используя знания из первого пункта, если фатальная ошибка имела место быть, предпринять необходимые действия;
- сообщение о фатальной ошибке в любом случае попадет в буфер вывода;
- воспользовавшись функциями контроля вывода можно предотвратить отображение нежелательной информации;
- при использовании оператора управления ошибками (знак @) функция, определенная в set_error_handler() все равно будет вызвана, но функция error_reporting() в этом случае вернет 0, чем и можно пользоваться для прекращения работы или определения другого поведения своего обработчика ошибок.
Третий пункт поясню: зарегистрированная нами функция при помощи register_shutdown_function() выполнится в любом случае — корректно ли завершился скрипт, либо же был прерван в связи с критичной (фатальной) ошибкой. Второй вариант мы можем однозначно определить, воспользовавшись информацией предоставленной функцией error_get_last(), и, если ошибка все же была, выполнить наш собственный обработчик ошибок.
Продемонстрируем вышесказанное на модифицированном скрипте index.php:
<?php
/**
* Обработчик ошибок
* @param int $errno уровень ошибки
* @param string $errstr сообщение об ошибке
* @param string $errfile имя файла, в котором произошла ошибка
* @param int $errline номер строки, в которой произошла ошибка
* @return boolean
*/
function error_handler($errno, $errstr, $errfile, $errline)
{
// если ошибка попадает в отчет (при использовании оператора "@" error_reporting() вернет 0)
if (error_reporting() & $errno)
{
$errors = array(
E_ERROR => 'E_ERROR',
E_WARNING => 'E_WARNING',
E_PARSE => 'E_PARSE',
E_NOTICE => 'E_NOTICE',
E_CORE_ERROR => 'E_CORE_ERROR',
E_CORE_WARNING => 'E_CORE_WARNING',
E_COMPILE_ERROR => 'E_COMPILE_ERROR',
E_COMPILE_WARNING => 'E_COMPILE_WARNING',
E_USER_ERROR => 'E_USER_ERROR',
E_USER_WARNING => 'E_USER_WARNING',
E_USER_NOTICE => 'E_USER_NOTICE',
E_STRICT => 'E_STRICT',
E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
E_DEPRECATED => 'E_DEPRECATED',
E_USER_DEPRECATED => 'E_USER_DEPRECATED',
);
// выводим свое сообщение об ошибке
echo "<b>{$errors[$errno]}</b>[$errno] $errstr ($errfile на $errline строке)<br />n";
}
// не запускаем внутренний обработчик ошибок PHP
return TRUE;
}
/**
* Функция перехвата фатальных ошибок
*/
function fatal_error_handler()
{
// если была ошибка и она фатальна
if ($error = error_get_last() AND $error['type'] & ( E_ERROR | E_PARSE | E_COMPILE_ERROR | E_CORE_ERROR))
{
// очищаем буффер (не выводим стандартное сообщение об ошибке)
ob_end_clean();
// запускаем обработчик ошибок
error_handler($error['type'], $error['message'], $error['file'], $error['line']);
}
else
{
// отправка (вывод) буфера и его отключение
ob_end_flush();
}
}
// определеяем уровень протоколирования ошибок
error_reporting(E_ALL | E_STRICT);
// определяем режим вывода ошибок
ini_set('display_errors', 'On');
// включаем буфферизацию вывода (вывод скрипта сохраняется во внутреннем буфере)
ob_start();
// устанавливаем пользовательский обработчик ошибок
set_error_handler("error_handler");
// регистрируем функцию, которая выполняется после завершения работы скрипта (например, после фатальной ошибки)
register_shutdown_function('fatal_error_handler');
require 'errors.php';
Не забываем, что ошибки смешанного типа, после объявления собственного обработчика ошибок, стали не фатальными. Плюс к этому, весь вывод скрипта до фатальной ошибки вместе с стандартным сообщением об ошибке будет сброшен.
Вообще, рассмотренный пример обработчика ошибок, обработкой, как таковой, не занимается, а только демонстрирует саму возможность. Дальнейшее его поведение зависит от ваших желаний и/или требований. Например, все случаи обращения к обработчику можно записывать в лог, а в случае фатальных ошибок, дополнительно, уведомлять об этом администратора ресурса.
Полезные ссылки
- Первоисточник: php.net/manual/ru/book.errorfunc.php
- Описание ошибок: php.net/manual/ru/errorfunc.constants.php
- Функции контроля вывода: php.net/manual/ru/ref.outcontrol.php
- Побитовые операторы: php.net/manual/ru/language.operators.bitwise.php и habrahabr.ru/post/134557
- Тематически близкая статья: habrahabr.ru/post/134499
Основы
PHP сообщает об ошибках в ответ на некоторые внутренние ошибочные обстоятельства.
Они могут быть использованы для уведомления о разных состояниях,
а также могут выводиться на экран и записываться в логи по желанию.
Каждая ошибка, генерируемая PHP, обязательно содержит информацию о своём типе.
В этом списке перечислены все типы ошибок,
а также описания их поведений и провоцирующие их причины.
Обработка ошибок
PHP, по умолчанию, может обрабатывать любые ошибки в соответствии со своей
конфигурацией, если обработчик ошибок не установлен. О каких ошибках сообщать
и какие ошибки игнорировать указывается в параметре error_reporting
конфигурации php.ini, или во время исполнения программы с помощью вызова
error_reporting(). Настоятельно рекомендуется заранее конфигурировать php.ini,
так как некоторые ошибки могут произойти до начала выполнения вашего скрипта.
На стадии разработки, обязательно настройте параметр
error_reporting
на значение E_ALL, так как вам необходимо знать обо всех ошибках
для их решения на этой стадии. Когда продукт готов, вы можете изменить значение на менее
подробное, вроде E_ALL & ~E_NOTICE & ~E_DEPRECATED,
но во многих случаях значение E_ALL также желательно, так как
оно может предупреждать заранее о других возможных ошибках.
Дальнейшая работа PHP с появившимися ошибками зависит от двух других параметров в php.ini.
Параметр display_errors
определяет, включать ли данные ошибки в вывод скрипта или нет. Когда продукт готов,
обязательно выключите этот параметр, так как в описании ошибки может содержаться
конфиденциальная информация, вроде паролей базы данных. Во время разработки данный
параметр лучше включить для решения проблем, провоцирующих ошибки.
Кроме отображения ошибок, их можно записывать в лог с помощью включённого параметра
log_errors.
Файл лога указывается в параметре error_log.
Данная функция незаменима на стадии конечного продукта, так как
позволяет видеть информацию об ошибках и составлять основанные на них
отчёты вдали от глаз пользователя.
Пользовательская обработка ошибок
Если вас не устраивает работа предустановленного обработчика ошибок,
вы также можете обрабатывать множество видов ошибки с помощью своего обработчика,
который можно установить вызовом функции set_error_handler().
Некоторые ошибки не могут обрабатываться пользовательским обработчиком,
но те, которые могут, обрабатываются по вашему желанию: например, ваш скрипт
может выдавать пользователю любую отдельную страницу ошибки, а подробную информацию
записывать в лог или отсылать по почте.
There are no user contributed notes for this page.
Антон Шевчук // Web-разработчик
Не совершает ошибок только тот, кто ничего не делает, и мы тому пример – трудимся не покладая рук над созданием рабочих мест для тестировщиков 
О да, в этой статье я поведу свой рассказа об ошибках в PHP, и том как их обуздать.
Ошибки
Разновидности в семействе ошибок
Перед тем как приручать ошибки, я бы рекомендовал изучить каждый вид и отдельно обратить внимание на самых ярких представителей.
Чтобы ни одна ошибка не ушла незамеченной потребуется включить отслеживание всех ошибок с помощью функции error_reporting(), а с помощью директивы display_errors включить их отображение:
<?php
error_reporting(E_ALL);
ini_set('display_errors', 1);
Фатальные ошибки
Самый грозный вид ошибок – фатальные, они могут возникнуть как при компиляции, так и при работе парсера или PHP-скрипта, выполнение скрипта при этом прерывается.
E_PARSE
Это ошибка появляется, когда вы допускаете грубую ошибку синтаксиса и интерпретатор PHP не понимает, что вы от него хотите, например если не закрыли фигурную или круглую скобочку:
<?php
/**
Parse error: syntax error, unexpected end of file
*/
{
Или написали на непонятном языке:
<?php /** Parse error: syntax error, unexpected '...' (T_STRING) */ Тут будет ошибка парсера
Лишние скобочки тоже встречаются, и не важно круглые либо фигурные:
<?php /** Parse error: syntax error, unexpected '}' */ }
Отмечу один важный момент – код файла, в котором вы допустили parse error не будет выполнен, следовательно, если вы попытаетесь включить отображение ошибок в том же файле, где возникла ошибка парсера то это не сработает:
<?php
// этот код не сработает
error_reporting(E_ALL);
ini_set('display_errors', 1);
// т.к. вот тут
ошибка парсера
E_ERROR
Это ошибка появляется, когда PHP понял что вы хотите, но сделать сие не получилось ввиду ряда причин, так же прерывает выполнение скрипта, при этом код до появления ошибки сработает:
Не был найден подключаемый файл:
/** Fatal error: require_once(): Failed opening required 'not-exists.php' (include_path='.:/usr/share/php:/usr/share/pear') */ require_once 'not-exists.php';
Было брошено исключение (что это за зверь, расскажу немного погодя), но не было обработано:
/** Fatal error: Uncaught exception 'Exception' */ throw new Exception();
При попытке вызвать несуществующий метод класса:
/** Fatal error: Call to undefined method stdClass::notExists() */ $stdClass = new stdClass(); $stdClass->notExists();
Отсутствия свободной памяти (больше, чем прописано в директиве memory_limit) или ещё чего-нить подобного:
/**
Fatal Error: Allowed Memory Size
*/
$arr = array();
while (true) {
$arr[] = str_pad(' ', 1024);
}
Очень часто происходит при чтении либо загрузки больших файлов, так что будьте внимательны с вопросом потребляемой памяти
Рекурсивный вызов функции. В данном примере он закончился на 256-ой итерации, ибо так прописано в настройках xdebug:
/**
Fatal error: Maximum function nesting level of '256' reached, aborting!
*/
function deep() {
deep();
}
deep();
Не фатальные
Данный вид не прерывает выполнение скрипта, но именно их обычно находит тестировщик, и именно они доставляют больше всего хлопот у начинающих разработчиков.
E_WARNING
Частенько встречается, когда подключаешь файл с использованием include, а его не оказывается на сервере или ошиблись указывая путь к файлу:
/** Warning: include_once(): Failed opening 'not-exists.php' for inclusion */ include_once 'not-exists.php';
Бывает, если используешь неправильный тип аргументов при вызове функций:
/**
Warning: join(): Invalid arguments passed
*/
join('string', 'string');
Их очень много, и перечислять все не имеет смысла…
E_NOTICE
Это самые распространенные ошибки, мало того, есть любители отключать вывод ошибок и клепают их целыми днями. Возникают при целом ряде тривиальных ошибок.
Когда обращаются к неопределенной переменной:
/** Notice: Undefined variable: a */ echo $a;
Когда обращаются к несуществующему элементу массива:
<?php /** Notice: Undefined index: a */ $b = array(); $b['a'];
Когда обращаются к несуществующей константе:
/** Notice: Use of undefined constant UNKNOWN_CONSTANT - assumed 'UNKNOWN_CONSTANT' */ echo UNKNOWN_CONSTANT;
Когда не конвертируют типы данных:
/** Notice: Array to string conversion */ echo array();
Для избежания подобных ошибок – будьте внимательней, и если вам IDE подсказывает о чём-то – не игнорируйте её:

E_STRICT
Это ошибки, которые научат вас писать код правильно, чтобы не было стыдно, тем более IDE вам эти ошибки сразу показывают. Вот например, если вызвали не статический метод как статику, то код будет работать, но это как-то неправильно, и возможно появление серьёзных ошибок, если в дальнейшем метод класса будет изменён, и появится обращение к $this:
/**
Strict standards: Non-static method Strict::test() should not be called statically
*/
class Strict {
public function test() {
echo 'Test';
}
}
Strict::test();
E_DEPRECATED
Так PHP будет ругаться, если вы используете устаревшие функции (т.е. те, что помечены как deprecated, и в следующем мажорном релизе их не будет):
/**
Deprecated: Function split() is deprecated
*/
// популярная функция, всё никак не удалят из PHP
// deprecated since 5.3
split(',', 'a,b');
В моём редакторе подобные функции будут зачёркнуты:

Обрабатываемые
Этот вид, которые разводит сам разработчик кода, я их уже давно не встречал, не рекомендую их вам заводить:
E_USER_ERROR– критическая ошибкаE_USER_WARNING– не критическая ошибкаE_USER_NOTICE– сообщения которые не являются ошибками
Отдельно стоит отметить E_USER_DEPRECATED – этот вид всё ещё используется очень часто для того, чтобы напомнить программисту, что метод или функция устарели и пора переписать код без использования оной. Для создания этой и подобных ошибок используется функция trigger_error():
/**
* @deprecated Deprecated since version 1.2, to be removed in 2.0
*/
function generateToken() {
trigger_error('Function `generateToken` is deprecated, use class `Token` instead', E_USER_DEPRECATED);
// ...
// code ...
// ...
}
Теперь, когда вы познакомились с большинством видов и типов ошибок, пора озвучить небольшое пояснение по работе директивы
display_errors:
- если
display_errors = on, то в случае ошибки браузер получит html c текстом ошибки и кодом 200- если же
display_errors = off, то для фатальных ошибок код ответа будет 500 и результат не будет возвращён пользователю, для остальных ошибок – код будет работать неправильно, но никому об этом не расскажет
Приручение
Для работы с ошибками в PHP существует 3 функции:
- set_error_handler() — устанавливает обработчик для ошибок, которые не обрывают работу скрипта (т.е. для не фатальных ошибок)
- error_get_last() — получает информацию о последней ошибке
- register_shutdown_function() — регистрирует обработчик который будет запущен при завершении работы скрипта. Данная функция не относится непосредственно к обработчикам ошибок, но зачастую используется именно для этого
Теперь немного подробностей об обработке ошибок с использованием set_error_handler(), в качестве аргументов данная функция принимает имя функции, на которую будет возложена миссия по обработке ошибок и типы ошибок которые будут отслеживаться. Обработчиком ошибок может так же быть методом класса, или анонимной функцией, главное, чтобы он принимал следующий список аргументов:
$errno– первый аргумент содержит тип ошибки в виде целого числа$errstr– второй аргумент содержит сообщение об ошибке$errfile– необязательный третий аргумент содержит имя файла, в котором произошла ошибка$errline– необязательный четвертый аргумент содержит номер строки, в которой произошла ошибка$errcontext– необязательный пятый аргумент содержит массив всех переменных, существующих в области видимости, где произошла ошибка
В случае если обработчик вернул true, то ошибка будет считаться обработанной и выполнение скрипта продолжится, иначе — будет вызван стандартный обработчик, который логирует ошибку и в зависимости от её типа продолжит выполнение скрипта или завершит его. Вот пример обработчика:
<?php
// включаем отображение всех ошибок, кроме E_NOTICE
error_reporting(E_ALL & ~E_NOTICE);
ini_set('display_errors', 1);
// наш обработчик ошибок
function myHandler($level, $message, $file, $line, $context) {
// в зависимости от типа ошибки формируем заголовок сообщения
switch ($level) {
case E_WARNING:
$type = 'Warning';
break;
case E_NOTICE:
$type = 'Notice';
break;
default;
// это не E_WARNING и не E_NOTICE
// значит мы прекращаем обработку ошибки
// далее обработка ложится на сам PHP
return false;
}
// выводим текст ошибки
echo "<h2>$type: $message</h2>";
echo "<p><strong>File</strong>: $file:$line</p>";
echo "<p><strong>Context</strong>: $". join(', $', array_keys($context))."</p>";
// сообщаем, что мы обработали ошибку, и дальнейшая обработка не требуется
return true;
}
// регистрируем наш обработчик, он будет срабатывать на для всех типов ошибок
set_error_handler('myHandler', E_ALL);
У вас не получится назначить более одной функции для обработки ошибок, хотя очень бы хотелось регистрировать для каждого типа ошибок свой обработчик, но нет – пишите один обработчик, и всю логику отображения для каждого типа описывайте уже непосредственно в нём
С обработчиком, который написан выше есть одна существенная проблема – он не ловит фатальные ошибки, и вместо сайта пользователи увидят лишь пустую страницу, либо, что ещё хуже, сообщение об ошибке. Дабы не допустить подобного сценария следует воспользоваться функцией register_shutdown_function() и с её помощью зарегистрировать функцию, которая всегда будет выполняться по окончанию работы скрипта:
function shutdown() {
echo 'Этот текст будет всегда отображаться';
}
register_shutdown_function('shutdown');
Данная функция будет срабатывать всегда!
Но вернёмся к ошибкам, для отслеживания появления в коде ошибки воспользуемся функцией error_get_last(), с её помощью можно получить информацию о последней выявленной ошибке, а поскольку фатальные ошибки прерывают выполнение кода, то они всегда будут выполнять роль “последних”:
function shutdown() {
$error = error_get_last();
if (
// если в коде была допущена ошибка
is_array($error) &&
// и это одна из фатальных ошибок
in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])
) {
// очищаем буфер вывода (о нём мы ещё поговорим в последующих статьях)
while (ob_get_level()) {
ob_end_clean();
}
// выводим описание проблемы
echo 'Сервер находится на техническом обслуживании, зайдите позже';
}
}
register_shutdown_function('shutdown');
Задание
Дополнить обработчик фатальных ошибок выводом исходного кода файла где была допущена ошибка, а так же добавьте подсветку синтаксиса выводимого кода.
О прожорливости
Проведём простой тест, и выясним – сколько драгоценных ресурсов кушает самая тривиальная ошибка:
/**
* Этот код не вызывает ошибок
*/
// сохраняем параметры памяти и времени выполнения скрипта
$memory = memory_get_usage();
$time= microtime(true);
$a = '';
$arr = [];
for ($i = 0; $i < 10000; $i++) {
$arr[$a] = $i;
}
printf('%f seconds <br/>', microtime(true) - $time);
echo number_format(memory_get_usage() - $memory, 0, '.', ' '), ' bytes<br/>';
В результате запуска данного скрипта у меня получился вот такой результат:
0.002867 seconds 984 bytes
Теперь добавим ошибку в цикле:
/**
* Этот код содержит ошибку
*/
// сохраняем параметры памяти и времени выполнения скрипта
$memory = memory_get_usage();
$time= microtime(true);
$a = '';
$arr = [];
for ($i = 0; $i < 10000; $i++) {
$arr[$b] = $i; // тут ошиблись с именем переменной
}
printf('%f seconds <br/>', microtime(true) - $time);
echo number_format(memory_get_usage() - $memory, 0, '.', ' '), ' bytes<br/>';
Результат ожидаемо хуже, и на порядок (даже на два порядка!):
0.263645 seconds 992 bytes
Вывод однозначен – ошибки в коде приводят к лишней прожорливости скриптов – так что во время разработки и тестирования приложения включайте отображение всех ошибок!
Тестирование проводил на PHP версии 5.6, в седьмой версии результат лучше – 0.0004 секунды против 0.0050 – разница только на один порядок, но в любом случае результат стоит прикладываемых усилий по исправлению ошибок
Где собака зарыта
В PHP есть спец символ «@» – оператор подавления ошибок, его используют дабы не писать обработку ошибок, а положится на корректное поведение PHP в случае чего:
<?php
echo @UNKNOWN_CONSTANT;
При этом обработчик ошибок указанный в set_error_handler() всё равно будет вызван, а факт того, что к ошибке было применено подавление можно отследить вызвав функцию error_reporting() внутри обработчика, в этом случае она вернёт 0.
Если вы в такой способ подавляете ошибки, то это уменьшает нагрузку на процессор в сравнении с тем, если вы их просто скрываете (см. сравнительный тест выше), но в любом случае, подавление ошибок это зло
Исключения
В эру PHP4 не было исключений (exceptions), всё было намного сложнее, и разработчики боролись с ошибками как могли, это было сражение не на жизнь, а на смерть… Окунуться в эту увлекательную историю противостояния можете в статье Исключительный код. Часть 1. Стоит ли её читать сейчас? Думаю да, ведь это поможет вам понять эволюцию языка, и раскроет всю прелесть исключений
Исключения — исключительные событие в PHP, в отличии от ошибок не просто констатируют наличие проблемы, а требуют от программиста дополнительных действий по обработке каждого конкретного случая.
К примеру, скрипт должен сохранить какие-то данные в кеш файл, если что-то пошло не так (нет доступа на запись, нет места на диске), генерируется исключение соответствующего типа, а в обработчике исключений принимается решение – сохранить в другое место или сообщить пользователю о проблеме.
Исключение – это объект который наследуется от класса Exception, содержит текст ошибки, статус, а также может содержать ссылку на другое исключение которое стало первопричиной данного. Модель исключений в PHP схожа с используемыми в других языках программирования. Исключение можно инициировать (как говорят, “бросить”) при помощи оператора throw, и можно перехватить (“поймать”) оператором catch. Код генерирующий исключение, должен быть окружен блоком try, для того чтобы можно было перехватить исключение. Каждый блок try должен иметь как минимум один соответствующий ему блок catch или finally:
try {
// код который может выбросить исключение
if (rand(0, 1)) {
throw new Exception('One')
} else {
echo 'Zero';
}
} catch (Exception $e) {
// код который может обработать исключение
echo $e->getMessage();
}
В каких случаях стоит применять исключения:
- если в рамках одного метода/функции происходит несколько операций которые могут завершиться неудачей
- если используемый вами фреймверк или библиотека декларируют их использование
Для иллюстрации первого сценария возьмём уже озвученный пример функции для записи данных в файл – помешать нам может очень много факторов, а для того, чтобы сообщить выше стоящему коду в чем именно была проблема необходимо создать и выбросить исключение:
$directory = __DIR__ . DIRECTORY_SEPARATOR . 'logs';
// директории может не быть
if (!is_dir($directory)) {
throw new Exception('Directory `logs` is not exists');
}
// может не быть прав на запись в директорию
if (!is_writable($directory)) {
throw new Exception('Directory `logs` is not writable');
}
// возможно кто-то уже создал файл, и закрыл к нему доступ
if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date('Y-m-d') . '.log', 'a+')) {
throw new Exception('System can't create log file');
}
fputs($file, date('[H:i:s]') . " donen");
fclose($file);
Соответственно ловить данные исключения будем примерно так:
try {
// код который пишет в файл
// ...
} catch (Exception $e) {
// выводим текст ошибки
echo 'Не получилось: '. $e->getMessage();
}
В данном примере приведен очень простой сценарий обработки исключений, когда у нас любая исключительная ситуация обрабатывается на один манер. Но зачастую – различные исключения требуют различного подхода к обработке, и тогда следует использовать коды исключений и задать иерархию исключений в приложении:
// исключения файловой системы
class FileSystemException extends Exception {}
// исключения связанные с директориями
class DirectoryException extends FileSystemException {
// коды исключений
const DIRECTORY_NOT_EXISTS = 1;
const DIRECTORY_NOT_WRITABLE = 2;
}
// исключения связанные с файлами
class FileException extends FileSystemException {}
Теперь, если использовать эти исключения то можно получить следующий код:
try {
// код который пишет в файл
if (!is_dir($directory)) {
throw new DirectoryException('Directory `logs` is not exists', DirectoryException::DIRECTORY_NOT_EXISTS);
}
if (!is_writable($directory)) {
throw new DirectoryException('Directory `logs` is not writable', DirectoryException::DIRECTORY_NOT_WRITABLE);
}
if (!$file = @fopen($directory . DIRECTORY_SEPARATOR . date('Y-m-d') . '.log', 'a+')) {
throw new FileException('System can't open log file');
}
fputs($file, date('[H:i:s]'') . " donen");
fclose($file);
} catch (DirectoryException $e) {
echo 'С директорией возникла проблема: '. $e->getMessage();
} catch (FileException $e) {
echo 'С файлом возникла проблема: '. $e->getMessage();
} catch (FileSystemException $e) {
echo 'Ошибка файловой системы: '. $e->getMessage();
} catch (Exception $e) {
echo 'Ошибка сервера: '. $e->getMessage();
}
Важно помнить, что Exception — это прежде всего исключительное событие, иными словами исключение из правил. Не нужно использовать их для обработки очевидных ошибок, к примеру, для валидации введённых пользователем данных (хотя тут не всё так однозначно). При этом обработчик исключений должен быть написан в том месте, где он будет способен его обработать. К примеру, обработчик для исключений вызванных недоступностью файла для записи должен быть в методе, который отвечает за выбор файла или методе его вызывающем, для того что бы он имел возможность выбрать другой файл или другую директорию.
Так, а что будет если не поймать исключение? Вы получите “Fatal Error: Uncaught exception …”. Неприятно.
Чтобы избежать подобной ситуации следует использовать функцию set_exception_handler() и установить обработчик для исключений, которые брошены вне блока try-catch и не были обработаны. После вызова такого обработчика выполнение скрипта будет остановлено:
// в качестве обработчика событий
// будем использовать анонимную функцию
set_exception_handler(function($exception) {
/** @var Exception $exception */
echo $exception->getMessage(), "<br/>n";
echo $exception->getFile(), ':', $exception->getLine(), "<br/>n";
echo $exception->getTraceAsString(), "<br/>n";
});
Ещё расскажу про конструкцию с использованием блока finally – этот блок будет выполнен вне зависимости от того, было выброшено исключение или нет:
try {
// код который может выбросить исключение
} catch (Exception $e) {
// код который может обработать исключение
// если конечно оно появится
} finally {
// код, который будет выполнен при любом раскладе
}
Для понимания того, что это нам даёт приведу следующий пример использования блока finally:
try {
// где-то глубоко внутри кода
// соединение с базой данных
$handler = mysqli_connect('localhost', 'root', '', 'test');
try {
// при работе с БД возникла исключительная ситуация
// ...
throw new Exception('DB error');
} catch (Exception $e) {
// исключение поймали, обработали на своём уровне
// и должны его пробросить вверх, для дальнейшей обработки
throw new Exception('Catch exception', 0, $e);
} finally {
// но, соединение с БД необходимо закрыть
// будем делать это в блоке finally
mysqli_close($handler);
}
// этот код не будет выполнен, если произойдёт исключение в коде выше
echo "Ok";
} catch (Exception $e) {
// ловим исключение, и выводим текст
echo $e->getMessage();
echo "<br/>";
// выводим информацию о первоначальном исключении
echo $e->getPrevious()->getMessage();
}
Т.е. запомните – блок finally будет выполнен даже в том случае, если вы в блоке catch пробрасываете исключение выше (собственно именно так он и задумывался).
Для вводной статьи информации в самый раз, кто жаждет ещё подробностей, то вы их найдёте в статье Исключительный код 
Задание
Написать свой обработчик исключений, с выводом текста файла где произошла ошибка, и всё это с подсветкой синтаксиса, так же не забудьте вывести trace в читаемом виде. Для ориентира – посмотрите как это круто выглядит у whoops.
PHP7 – всё не так, как было раньше
Так, вот вы сейчас всю информацию выше усвоили и теперь я буду грузить вас нововведениями в PHP7, т.е. я буду рассказывать о том, с чем вы столкнётесь через год работы PHP разработчиком. Ранее я вам рассказывал и показывал на примерах какой костыль нужно соорудить, чтобы отлавливать критические ошибки, так вот – в PHP7 это решили исправить, но как обычно завязались на обратную совместимость кода, и получили хоть и универсальное решение, но оно далеко от идеала. А теперь по пунктам об изменениях:
- при возникновении фатальных ошибок типа
E_ERRORили фатальных ошибок с возможностью обработкиE_RECOVERABLE_ERRORPHP выбрасывает исключение - эти исключения не наследуют класс Exception (помните я говорил об обратной совместимости, это всё ради неё)
- эти исключения наследуют класс Error
- оба класса Exception и Error реализуют интерфейс Throwable
- вы не можете реализовать интерфейс Throwable в своём коде
Интерфейс Throwable практически полностью повторяет нам Exception:
interface Throwable
{
public function getMessage(): string;
public function getCode(): int;
public function getFile(): string;
public function getLine(): int;
public function getTrace(): array;
public function getTraceAsString(): string;
public function getPrevious(): Throwable;
public function __toString(): string;
}
Сложно? Теперь на примерах, возьмём те, что были выше и слегка модернизируем:
try {
// файл, который вызывает ошибку парсера
include 'e_parse_include.php';
} catch (Error $e) {
var_dump($e);
}
В результате ошибку поймаем и выведем:
object(ParseError)#1 (7) {
["message":protected] => string(48) "syntax error, unexpected 'будет' (T_STRING)"
["string":"Error":private] => string(0) ""
["code":protected] => int(0)
["file":protected] => string(49) "/www/education/error/e_parse_include.php"
["line":protected] => int(4)
["trace":"Error":private] => array(0) { }
["previous":"Error":private] => NULL
}
Как видите – поймали исключение ParseError, которое является наследником исключения Error, который реализует интерфейс Throwable, в доме который построил Джек. Ещё есть другие, но не буду мучать – для наглядности приведу иерархию исключений:
interface Throwable
|- Exception implements Throwable
| |- ErrorException extends Exception
| |- ... extends Exception
| `- ... extends Exception
`- Error implements Throwable
|- TypeError extends Error
|- ParseError extends Error
|- ArithmeticError extends Error
| `- DivisionByZeroError extends ArithmeticError
`- AssertionError extends Error
TypeError – для ошибок, когда тип аргументов функции не совпадает с передаваемым типом:
try {
(function(int $one, int $two) {
return;
})('one', 'two');
} catch (TypeError $e) {
echo $e->getMessage();
}
ArithmeticError – могут возникнуть при математических операциях, к примеру когда результат вычисления превышает лимит выделенный для целого числа:
try {
1 << -1;
} catch (ArithmeticError $e) {
echo $e->getMessage();
}
DivisionByZeroError – ошибка деления на ноль:
try {
1 / 0;
} catch (ArithmeticError $e) {
echo $e->getMessage();
}
AssertionError – редкий зверь, появляется когда условие заданное в assert() не выполняется:
ini_set('zend.assertions', 1);
ini_set('assert.exception', 1);
try {
assert(1 === 0);
} catch (AssertionError $e) {
echo $e->getMessage();
}
При настройках production-серверов, директивы
zend.assertionsиassert.exceptionотключают, и это правильно
Задание
Написать универсальный обработчик ошибок для PHP7, который будет отлавливать все возможные исключения.
При написании данного раздела были использованы материалы из статьи Throwable Exceptions and Errors in PHP 7
Отладка
Иногда для отладки кода нужно отследить что происходило с переменной или объектом на определённом этапе, для этих целей есть функция debug_backtrace() и debug_print_backtrace() которые вернут историю вызовов функций/методов в обратном порядке:
<?php
function example() {
echo '<pre>';
debug_print_backtrace();
echo '</pre>';
}
class ExampleClass {
public static function method () {
example();
}
}
ExampleClass::method();
В результате выполнения функции debug_print_backtrace() будет выведен список вызовов приведших нас к данной точке:
#0 example() called at [/www/education/error/backtrace.php:10] #1 ExampleClass::method() called at [/www/education/error/backtrace.php:14]
Проверить код на наличие синтаксических ошибок можно с помощью функции php_check_syntax() или же команды php -l [путь к файлу], но я не встречал использования оных.
Assert
Отдельно хочу рассказать о таком экзотическом звере как assert() в PHP, собственно это кусочек контрактной методологии программирования, и дальше я расскажу вам как я никогда его не использовал 
Первый случай – это когда вам надо написать TODO прямо в коде, да так, чтобы точно не забыть реализовать заданный функционал:
// включаем вывод ошибок
error_reporting(E_ALL);
ini_set('display_errors', 1);
// включаем asserts
ini_set('zend.assertions', 1);
ini_set('assert.active', 1);
assert(false, "Remove it!");
В результате выполнения данного кода получим E_WARNING:
Warning: assert(): Remove it! failed
PHP7 можно переключить в режим exception, и вместо ошибки будет всегда появляться исключение AssertionError:
// включаем asserts
ini_set('zend.assertions', 1);
ini_set('assert.active', 1);
// переключаем на исключения
ini_set('assert.exception', 1);
assert(false, "Remove it!");
В результате ожидаемо получаем не пойманный AssertionError. При необходимости, можно выбрасывать произвольное исключение:
assert(false, new Exception("Remove it!"));
Но я бы рекомендовал использовать метки
@TODO, современные IDE отлично с ними работают, и вам не нужно будет прикладывать дополнительные усилия и ресурсы для работы с ними
Второй вариант использования – это создание некоего подобия TDD, но помните – это лишь подобие. Хотя, если сильно постараться, то можно получить забавный результат, который поможет в тестировании вашего кода:
// callback-функция для вывода информации в браузер
function backlog($script, $line, $code, $message) {
echo "<h3>$message</h3>";
highlight_string ($code);
}
// устанавливаем callback-функцию
assert_options(ASSERT_CALLBACK, 'backlog');
// отключаем вывод предупреждений
assert_options(ASSERT_WARNING, false);
// пишем проверку и её описание
assert("sqr(4) == 16", "When I send integer, function should return square of it");
// функция, которую проверяем
function sqr($a) {
return; // она не работает
}
Третий теоретический вариант – это непосредственно контрактное программирование – когда вы описали правила использования своей библиотеки, но хотите точно убедится, что вас поняли правильно, и в случае чего сразу указать разработчику на ошибку (я вот даже не уверен, что правильно его понимаю, но пример кода вполне рабочий):
/**
* Настройки соединения должны передаваться в следующем виде
*
* [
* 'host' => 'localhost',
* 'port' => 3306,
* 'name' => 'dbname',
* 'user' => 'root',
* 'pass' => ''
* ]
*
* @param $settings
*/
function setupDb ($settings) {
// проверяем настройки
assert(isset($settings['host']), 'Db `host` is required');
assert(isset($settings['port']) && is_int($settings['port']), 'Db `port` is required, should be integer');
assert(isset($settings['name']), 'Db `name` is required, should be integer');
// соединяем с БД
// ...
}
setupDb(['host' => 'localhost']);
Никогда не используйте
assert()для проверки входных параметров, ведь фактическиassert()интерпретирует строковую переменную (ведёт себя какeval()), а это чревато PHP-инъекцией. И да, это правильное поведение, т.к. просто отключив assert’ы всё что передаётся внутрь будет проигнорировано, а если делать как в примере выше, то код будет выполняться, а внутрь отключенного assert’a будет передан булевый результат выполнения
Если у вас есть живой опыт использования assert() – поделитесь со мной, буду благодарен. И да, вот вам ещё занимательно чтива по этой теме – PHP Assertions, с таким же вопросом в конце 
В заключение
Я за вас напишу выводы из данной статьи:
- Ошибкам бой – их не должно быть в вашем коде
- Используйте исключения – работу с ними нужно правильно организовать и будет счастье
- Assert – узнали о них, и хорошо
P.S. Спасибо Максиму Слесаренко за помощь в написании статьи
Отладка программ

- PHP и MySQL
- Основы PHP
- Отладка программ
Внимание! Данный курс устарел!
Переходите к новому курсу «PHP для начинающих».
Отладка (поиск и устранение ошибок) представляет собой неотъемлемую часть разработки программного обеспечения. Программист, работающий на языке PHP, должен быть знаком со всеми доступными инструментальными средствами, позволяющими выявлять неправильно функционирующие компоненты в программных системах.
Количество инструментальных средств отладки достаточно велико. Не в последнюю очередь такая ситуация объясняется тем, что в приложениях PHP обычно используются возможности нескольких серверов (таких как сервер HTTP и сервер, входящий в состав системы управления базами данных), а в комплект каждого из таких серверов обычно входят собственные компоненты ведения журналов и формирования сообщений об ошибках, с помощью которых эти серверы предоставляют своим пользователям возможность следить за происходящим.
Кроме того, система PHP имеет собственные развитые средства формирования сообщений об ошибках (они позволяют организовать вывод сообщений об ошибках вместе с обычными выходными данными или регистрировать эти сообщения в файле для более тщательного анализа). К тому же большое количество функций, позволяющих обеспечить выработку в программах специализированных отчетов об обнаруженных ошибках, предусмотрено в самом языке PHP. По крайней мере, всегда есть возможность использовать в программах на языке PHP условные операторы вывода для контроля над действиями, осуществляемыми в программах в ходе их выполнения (и над значениями переменных в программах).
Кроме встроенных средств формирования сообщений об ошибках языка PHP и технологий, поддерживаемых этим языком, программисты, работающие на языке PHP, в последнее время получили возможность использовать такие же разновидности инструментальных средств отладки, которые в течение многих лет находились в распоряжении программистов, работающих на других языках. Основной среди этих инструментальных средств является среда отладки Zend, которая позволяет контролировать значения переменных, устанавливать точки прерывания и обеспечивать пошаговое выполнение программ с любой желаемой скоростью. В этой статье среда отладки Zend не рассматривается, но ее описание и другие дополнительные сведения можно найти по адресу www.zend.com.
В этой статье приведено лишь вводное описание инструментальных средств и методов, которыми может воспользоваться разработчик, стремящийся создать безукоризненно действующее программное обеспечение на языке PHP.

Общие стратегии поиска неисправностей
Двумя основными составляющими всей деятельности по отладке являются обнаружение причин нарушения в работе и последующее их устранение (без нарушения функционирования чего-либо иного под воздействием побочных эффектов реализации принятого решения). Это утверждение остается справедливым независимо от того, осуществляется ли диагностика программы PHP, телефонного коммутатора, электронной схемы или автомобиля «Копейка», — определенные принципы остаются в силе, о какой бы рассматриваемой проблемной области не шла речь. Всегда руководствуйтесь таким подходом, пытаясь выяснить, в чем причина нарушений в работе рассматриваемого программного обеспечения.
Внесение изменений только в одном месте
При проведении любых экспериментов необходимо руководствоваться основным правилом: если функционирование рассматриваемого объекта зависит от многочисленных факторов, то невозможно заранее узнать со всей определенностью, чем вызван какой-то конкретный сбой. Поэтому всегда следует вносить только одно изменение, затем проверять полученные результаты и определять, удалось ли устранить нежелательное поведение объекта. В случае отрицательного ответа необходимо внести еще одно изменение (возможно, полностью отменив внесенное перед этим изменение).
Ограничение области проявления проблемы
Если удастся свести поиск причин проблемы до единственной библиотеки или функции, это можно рассматривать как значительный успех в процессе поиска источника проблемы. Используйте специальные вызовы функций echo() и print_r() для своевременного вывода информации трассировки. Это позволяет определить тот момент, когда возникают изменения, являющиеся причиной нарушения в работе, и в какое время переменные приобретают такие значения, которые не должны были содержать.
Кроме того, для контроля над функционированием программ и за поведением компонентов программ в ходе их функционирования можно использовать отладчик с графическим интерфейсом (такой как Zend Studio).
Упрощение и последующее усложнение
Эта рекомендация может показаться очевидной, но о ней часто забывают. Если приходится сталкиваться с нарушениями в работе, связанными с использованием какой-то конкретной функции или средства, соответствующий компонент необходимо исключить из программы (либо заменив фиктивным вызовом, либо обозначив комментариями) и проверить, позволяет ли это добиться нормального функционирования программы. Еще один вариант состоит в том, чтобы заменить динамические данные статическими данными (например, вместо получения данных с помощью запроса к базе данных применить простые операторы присваивания значений переменным). Добейтесь успешного функционирования программы в наиболее упрощенных условиях, а затем поэтапно усложняйте программу, каждый раз проводя проверку, чтобы узнать, на каком этапе обнаруживаются ошибки.
Документирование принятых решений
Такая ситуация встречается слишком часто: программист часами отыскивает причину ошибки (или даже откладывает эту работу на следующий день) и наконец находит решение. Не следует после этого сразу же отправляться праздновать победу. Найдите время, чтобы отразить в комментариях к программе то, в чем состояла ошибка и каковым является решение. Это позволит вам быть во всеоружии, если снова возникнет та же проблема, а она непременно возникнет.
Повторная проверка после исправления ошибок
Нередко встречается такая ситуация, когда устранение проблемы в одном компоненте приводит к нарушению в работе какого-то другого компонента. Именно поэтому необходимо еще раз проверить систему, чтобы убедиться в ее нормальной работе не только в том месте, где была первоначально обнаружена ошибка, но и во всех других местах, где могут возникнуть нарушения. Данная рекомендация позволяет также понять, почему так важно ограничивать область действия ошибок в максимально возможной степени, — это дает возможность уменьшить объем необходимого повторного тестирования.

Общая классификация ошибок
Программистам приходится сталкиваться с весьма разнообразными ошибками. Некоторые ошибки являются не только простыми по своему характеру, но и легко обнаруживаемыми (к ним относятся синтаксические ошибки и ошибки, связанные с неправильным написанием). Задача поиска других ошибок является намного более сложной, поэтому рекомендации, приведенные в этом разделе, помогут многим программистам.
Ошибки на этапе компиляции
Язык PHP является компилируемым, но компиляция программы осуществляется непосредственно перед ее выполнением, поэтому сам процесс компиляции не столь очевиден, как в языке C или Java.
Ошибки, возникающие на этапе компиляции, обнаруживаются машиной Zend Engine, которая осуществляет компиляцию. Компилятор формирует сообщение об ошибке, часто указывая номер строки, и после получения такого сообщения программист может приступить к устранению проблемы. К категории ошибок, обнаруживаемых на этапе компиляции, относятся неправильно введенные имена переменных, отсутствующие точки с запятой и несогласованные круглые скобки.
Ошибки этапа выполнения
Ошибка этапа выполнения не обнаруживается до тех пор, пока не начинается эксплуатация программы. Причиной такой ошибки могут стать какие-то внешние факторы, такие как неправильно введенные пользователем данные или непредвиденные результаты, возвращенные из базы данных. Подобные ошибки выявляются только с помощью тестирования, поскольку обычно программист не имеет возможности проанализировать все условия, при которых эти ошибки стали бы очевидными.
Логические ошибки
По-видимому, логические ошибки относятся к категории ошибок, наиболее трудно поддающихся обнаружению. А если первоисточником подобной ошибки является неправильное понимание программистом каких-то аспектов решаемой задачи, то такая ошибка очень сложно поддается исправлению.
Предположим, перед программистом поставлена задача обеспечить запуск космического зонда и вывести его на орбиту вокруг Марса. Программист из США предусмотрел в своем навигационном алгоритме получение входных данных в фунтах и дюймах, но требуемые данные поступают из европейского центра управления полетами в метрической системе. Очевидно, что при таких условиях космический зонд непременно врежется в марсианскую поверхность. Программное обеспечение действовало в полном соответствии с заданием, но, строго говоря, при таких условиях задание предусматривало вывод ракеты прямо на Марс. Это — логическая ошибка.
Из этого следует, что необходимо добиться того, чтобы программа не просто вырабатывала выходные данные, но вырабатывала их правильно. Воспользуйтесь отдельно взятой процедурой расчетов и убедитесь в том, что формируемые программой результаты являются правильными; еще один вариант состоит в том, что результаты программы должны сравниваться с заведомо известными правильными значениями. А для того чтобы не попадать в неприятную историю, не пишите программы на языке PHP для космических кораблей ;).

Использование журналов веб-сервера
Эксплуатация большинства программ PHP приводит к получению HTML-страниц того или иного рода, а эти страницы, в свою очередь, передаются пользователю с помощью HTTP-сервера, такого как Apache или Microsoft Internet Information Server (IIS). Это означает, что дополнительным источником ошибок может стать программное обеспечение веб-сервера. По этой причине важно знать, какой способ применяется в веб-сервере для формирования и регистрации сообщений об ошибках, а также знать о том, как получить доступ и провести синтаксический анализ журналов, в которых регистрируются ошибки.
Сервер Apache
На HTTP-сервере Apache ведутся два файла журнала в формате открытого текста. Эти журналы описаны ниже:
- Apache/logs/access.log
-
Предназначен для регистрации каждого запроса на получение файла, передаваемого по протоколу HTTP. К регистрируемым данным относятся дата, время и полученные результаты (успешное или неудачное завершение, о чем можно судить по числовому коду состояния). Этот журнал представляет собой также журнал регистрации доступа, в котором фиксируется IP-адрес, из которого поступил каждый запрос.
- Apache/logs/error.log
-
Это — журнал регистрации ошибок, в котором фиксируются только ситуации, связанные с возникновением ошибок.
Унифицированный формат журнала
По умолчанию для оформления записей в файле error.log сервера Apache используется унифицированный формат журнала, который принят в качестве стандартного. В этом формате каждая запись соответствует отдельному экземпляру действия, касающегося запроса и/или ответа (в конечном итоге HTTP-серверы занимаются обработкой именно запросов и ответов). Например, одна строка журнала может соответствовать запросу на получение HTML-страницы (и содержать сведения о том, в каких обстоятельствах эта страница была впоследствии предоставлена сервером Apache). А следующая строка может описывать (автоматическое) выполнение запроса и передачу файла JPEG, связанного с ранее затребованным документом HTML.
В любом случае записи в унифицированном формате журнала выглядят примерно так, как показано ниже (в действительности каждая запись занимает только одну строку):
127.0.0.1 - - [12/Jan/2013:03:18:33 +0800] "GET /images/lang.gif HTTP/1.1" 200 6590
Ниже перечислены наиболее важные компоненты данной строки:
-
127.0.0.1. IP-адрес клиента, от которого поступил запрос по протоколу HTTP (в данном случае локальный хост).
-
[12/Jan/2013:03:18:33 +0400]. Дата, время и разница между временем текущего часового пояса и всеобщим скоординированным временем (Universal Coordinated Time — UTC).
-
GET. Тип запроса HTTP — GET или POST.
-
/images/lang.gif. Затребованный файл.
-
HTTP/1.1. Версия протокола HTTP, используемого для передачи запроса.
-
200. Код ответа, описывающий результат запроса (дополнительная информация на эту тему приведена ниже в данном разделе).
-
6590. Количество байтов, переданных в ответе HTTP, соответствующем этому запросу.
Коды ответов HTTP
Количество кодов ответов HTTP весьма велико (хотя самым знаменитым остается код сообщения об ошибке «404 Not Found»), но сами эти коды подчиняются общему шаблону, с помощью которого можно быстро узнать назначение каждого кода. По существу, данные шаблоны подразделяются на категории, описанные ниже:
-
Коды ряда 200 указывают на успешное завершение.
-
Коды ряда 300 обозначают перенаправление.
-
Коды ряда 400 указывают на клиентскую ошибку (подобную указанию в запросе несуществующего документа).
-
Коды ряда 500 указывают на серверную ошибку.
Полный список кодов ответов HTTP приведен по адресу www.w3.org/Protocols/rfc2616/rfc2616-sec10.html.
Сервер IIS
В HTTP-сервере IIS компании Microsoft задача ведения журнала осуществляется иначе. Сервер IIS не записывает журнал в файл, а регистрирует полученную им информацию о состоянии и об обнаруженных ошибках таким образом, чтобы эта информация была доступна для исследования в программе Event Viewer. Ошибки, зарегистрированные сервером IIS, можно найти в части System Log окна Event Viewer, где для обозначения источника применяется имя W3SVC.

Неоценимую помощь при выявлении ошибок может оказать сам интерпретатор PHP. Даже без какой-либо дополнительной настройки система PHP способна выводить сообщения об ошибках вместе с выходными данными. Эти сообщения передаются прямо в окно браузера вместе с номерами строк. Для большинства программистов этих сведений вполне достаточно, чтобы можно было воспользоваться самим интерпретатором PHP как средством отладки, но, чтобы получить максимум пользы от этих средств, важно знать все нюансы настройки конфигурации, от которых зависят условия формирования сообщений об ошибках.
Безусловно, интерпретатор PHP показывает номер строки, в которой была обнаружена ошибка, но следует знать о том, что этот номер не всегда указывает на ту строку, к которой следует перейти, чтобы внести исправление. Иногда отсутствующая закрывающая кавычка или пропущенная точка с запятой не обнаруживается интерпретатором до завершения обработки нескольких следующих строк, поэтому нужно быть готовым к тому, что придется вернуться немного назад, чтобы найти причины синтаксических ошибок такого рода.
Формирование сообщений об ошибках
Выполняемая интерпретатором PHP операция включения сообщения об ошибке в вывод программы (что чаще всего приводит к отображению сообщения об ошибке в окне браузера) по существу представляет собой операцию формирования сообщения об ошибке. Формирование сообщений об ошибках является полезным диагностическим инструментальным средством, использование которого разрешено по умолчанию. Если же интерпретатор PHP подключен к серверу производственного назначения, то данное средство должно быть запрещено.
Чтобы разрешить или запретить формирование сообщений об ошибках, необходимо внести изменение в файл php.ini. При этом корректировка применяется к параметру display_errors. Если требуется, чтобы сообщения выводились в составе формируемых выходных данных, то в файле php.ini должна присутствовать следующая строка:
display_errors=On
Если же сообщения об ошибках не должны отображаться (и это действительно не допустимо, если сервер используется для предоставления к нему общего доступа), то соответствующая строка должна выглядеть таким образом:
display_errors=Off
Игнорирование данной рекомендации при эксплуатации сервера производственного назначения приводит к тому, что функционирование средств формирования сообщений об ошибках вызовет непреднамеренное раскрытие перед пользователями важных сведений об организации программного обеспечения. Например, какое-либо непредвиденное условие может вызвать появление в окне незащищенного браузера имени переменной или таблицы базы данных. А потенциальный нарушитель сможет воспользоваться этой информацией для несанкционированного доступа к серверу.
Регистрация ошибок
Регистрация ошибок аналогична по своему назначению формированию сообщений об ошибках, но предусматривает запись информации о событиях, связанных с ошибками, в текстовый файл, а не вывод этой информации на экран. Такой вариант представления сведений об ошибках более приемлем с точки зрения защиты. Кроме того, поскольку файлы журналов должны храниться в каталоге с ограниченным доступом, этот метод регистрации ошибок более предпочтителен для использования на HTTP-серверах производственного назначения.
Как и в случае средств формирования сообщений об ошибках, применение средств регистрации ошибок может быть разрешено или запрещено с помощью файла php.ini. Чтобы разрешить использование этих средств, необходимо применить следующую опцию:
log_errors=On
В противном случае следует задать такую опцию:
log_errors=Off
По умолчанию средства регистрации ошибок запрещены в файле php.ini.
Определение категории ошибок, подлежащих выводу на экран или записи в журнал
Принимая решение об использовании средств формирования сообщений об ошибках (для вывода на экран) или средств регистрации ошибок (для записи в файл), необходимо дополнительно указать, какие ошибки рассматриваются как достаточно серьезные для предоставления информации о них пользователю. Параметры настройки средств ведения журнала задаются в файле php.ini с помощью значения параметра error_reporting. По умолчанию параметру error_reporting присваивается следующее значение:
error_reporting=E_ALL & ~E_NOTICE
Такая настройка указывает, что пользователю должна предоставляться информация обо всех ошибках и предупреждениях (что показывает параметр E_ALL), но вместе с тем действует условие (что показывает оператор &), согласно которому извещения этапа прогона не подлежат выводу (для этого служит параметр ~E_NOTICE, где ~ представляет собой оператор отрицания).
Степень серьезности отображаемых сообщений об ошибках, которая определена параметром error_reporting, отражается на поведении средств регистрации ошибок (если их использование разрешено с помощью параметра log_errors=On) и средств формирования сообщений об ошибках (если их использование разрешено с помощью параметра display_errors=On) или одновременно тех и других средств, если все они разрешены.

Функции формирования сообщений об ошибках
Язык PHP не только в значительной степени упрощает работу программистов, но и включает в себя широкий набор функций, которые могут использоваться программистами для облегчения поиска причин нарушений в работе и в целом для получения сведений обо всех аспектах состояния разрабатываемых ими программ. Спектр таких средств начинается с обычных операторов формирования вывода (print, echo и тому подобных), используемых в тех контекстах, когда требуется контролировать значения переменных, и заканчивается специализированными функциями, осуществляющими вывод данных с помощью механизмов ведения журналов операционной системы.
В настоящем разделе даны вводные сведения о некоторых функциях PHP, которые позволяют выявлять причины проблем и создавать отчеты об условиях функционирования программ.
Диагностические операторы вывода
Простейший метод устранения нарушений в работе предусматривает размещение в ключевых местах кода операторов echo и print, для того чтобы формируемые выходные данные содержали информацию о ходе выполнения программы, осуществляемого на основе вызова различных функций, а также о том, какие значения принимают наиболее важные переменные в различных точках.
Использование таких средств можно сравнить с применением простейшего отладчика, поскольку операторы вывода позволяют отслеживать значения переменных во время выполнения программы и следить за тем, не происходит ли присваивание этим переменным каких-либо непредусмотренных значений (и в случае положительного ответа на этот вопрос позволяют узнать, когда это происходит).
Использование функции var_dump()
Обычные функции вывода являются довольно удобными, но с точки зрения отладки более специализированные функции может оказаться гораздо полезнее. Наиболее важной среди функций подобного типа является функция var_dump(). Она может рассматриваться как чрезвычайно «остроумный» оператор вывода, который, кроме всего прочего, автоматически представляет содержимое любого массива в таком виде, который очень удобен для восприятия человеком.
Напомним, что выполнение приведенного ниже кода приводит к получению результатов, от которых мало пользы:
Код PHP
$fruit = array('orange' => 'Апельсин',
'red' => 'Яблоко',
'yellow' => 'Банан',
'green' => 'Груша');
echo $fruit;
Данные результаты сводятся к следующему:

Это нам ничего не дает. С другой стороны, если за определением того же массива следует строка
Код PHP
var_dump($fruit);
то формируется следующие намного более полезные выходные данные:

На основании таких данных программист, занимающийся отладкой, может немедленно определить, каково содержимое данного массива (чему равны ключи и значения элементов этого массива).
Использование функции syslog()
В языке PHP предусмотрена функция syslog(), которая позволяет осуществлять запись непосредственно в журнал операционной системы, под управлением которой функционирует среда PHP. Это удобная функция, которая становится особенно полезной, если требуется регистрировать всю информацию о проблемах, возникающих в системе, с помощью стандартных средств, или есть необходимость предупредить о нарушении в работе системного администратора, если он непосредственно не соприкасается с разработками на языке PHP.
В целом функция syslog() позволяет указать степень серьезности, связанную с регистрируемым событием, а также ввести сообщение, которое описывает это событие. Затем указанные значения могут быть выведены в журнал для использования в качестве вспомогательной информации для диагностики.
Все возможные опции определения степени серьезности функции syslog() показаны в следующем коде:
Код PHP
$logOptions = array(LOG_DEBUG, LOG_INFO, LOG_NOTICE, LOG_WARNING,
LOG_ERR, LOG_CRIT, LOG_ALERT, LOG_EMERG);
$msg = array('Сообщение отладки', 'Информация', 'Уведомление', 'Предупреждение',
'Ошибка', 'Критическая ошибка', 'Серьезная ошибка', 'Кабздец');
foreach($logOptions as $key => $value) {
syslog($value, $msg[$key]);
}
В системе Microsoft Windows первые три из этих сообщений об ошибках (от LOG_DEBUG до LOG_NOTICE) рассматриваются как информационные, четвертое и пятое считаются предупреждающими, а последние три отмечаются в программе Event Viewer как относящиеся к категории Alerts. Все эти сообщения отображаются со значением источника c-client, которое соответствует одному из вспомогательных процессов сервера Apache:

Использование функции error_log()
Функция error_log() может использоваться для передачи сообщения об ошибке почти в любое место назначения, включая адрес электронной почты. Эта функция предоставляет легкий и удобный способ формирования отчета о непредвиденных условиях, возникших в процессе функционирования программного обеспечения PHP, но, к сожалению, этим средством пользуются лишь немногие разработчики. Более подробно она описана в предыдущей статье в разделе «Ведение журнала и отладка».
