Ошибки PHP с кодами: управление обработчиками и журналом

Раздел: PHP -> Ошибки и отладка PHP

Код ошибки PHP: типы, вывод и перехват

Каждая ошибка PHP имеет числовой код, который указывает на серьёзность проблемы. Код используется внутри интерпретатора, в сообщениях об ошибках и в пользовательских обработчиках. Понимание этих кодов позволяет настроить вывод и запись событий так, чтобы отладка не мешала работе сайта.

Как включить показ и журналирование ошибок с их кодами?

Основной подход для локальной разработки состоит в том, чтобы включить вывод всех ошибок и одновременно записывать их в файл. Для боевого сервера вывод на экран лучше отключить, оставив только журнал.

error_reporting(E_ALL);
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
ini_set('log_errors', '1');
ini_set('error_log', __DIR__ . '/php-error.log');

Php код ошибки (код ошибки php)

Первая команда задаёт битовую маску E_ALL. Она включает все известные уровни ошибок. Затем display_errors разрешает печать ошибок в браузер или консоль. display_startup_errors действует на ошибки, возникающие при старте PHP. log_errors включает запись в файл, имя которого задаёт error_log.

Почему сообщение с кодом ошибки не появляется?

Если error_reporting включён, но текст не виден, причина обычно в значении display_errors. На некоторых хостингах ini_set('display_errors', '1') запрещено. Тогда ошибку можно увидеть только в журнале. Для этого log_errors должен быть равен 1, а error_log указывать на доступный для записи файл.

Как отфильтровать определённые коды ошибок?

Коды ошибок объединяются в битовые маски. Для отключения уведомлений и замечаний об устаревшем коде используется исключающий оператор.

error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);

Php mysql ошибки (ошибки php mysql)

Такой вариант подходит для проектов, где старый код генерирует много второстепенных предупреждений. Он отключает только уведомления E_NOTICE и E_DEPRECATED, оставляя остальные ошибки видимыми.

Как перехватить код ошибки и преобразовать его в исключение?

Для нефатальных ошибок можно установить обработчик, который выбрасывает ErrorException. Это позволяет применять единый механизм try/catch.

set_error_handler(function ($severity, $message, $file, $line) {
    if (!(error_reporting() & $severity)) {
        return false;
    }
    throw new ErrorException($message, $severity, $severity, $file, $line);
});

try {
    trigger_error('Пользовательская ошибка', E_USER_NOTICE);
} catch (ErrorException $e) {
    echo 'Код ' . $e->getCode() . ': ' . $e->getMessage();
}

В обработчике проверяется текущая маска error_reporting. Если код не входит в маску, возвращается false, и PHP использует стандартную обработку. Когда обработчик выбрасывает исключение, код попадает в catch, где через getCode() доступен числовой код.

Как определить код фатальной ошибки после остановки скрипта?

Фатальные ошибки не проходят через set_error_handler. Их отслеживает функция, зарегистрированная через register_shutdown_function. Ошибка берётся из error_get_last().

register_shutdown_function(function () {
    $last = error_get_last();
    if ($last !== null) {
        echo 'Фатальный код: ' . $last['type'] . PHP_EOL;
        echo 'Сообщение: ' . $last['message'];
    }
});

После завершения скрипта error_get_last() возвращает массив с последней ошибкой. Если остановка вызвана E_ERROR, в массиве будет тип E_ERROR. Такой же способ работает для E_PARSE и E_COMPILE_ERROR.

Как создать собственную ошибку с нужным кодом?

Функция trigger_error позволяет вызвать ошибку с одним из пользовательских кодов: E_USER_ERROR, E_USER_WARNING или E_USER_NOTICE.

trigger_error('Недостаточно данных', E_USER_WARNING);

Это удобно для проверки обработчиков и логирования. Сторонний код тоже может вызывать такие ошибки, если в нём есть вызовы trigger_error.

Почему set_error_handler не обрабатывает фатальные ошибки?

Обработчик ошибок в PHP не вызывается для E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR. Для этих кодов применяется shutdown-перехват. В PHP 7 и новее многие фатальные сбои представлены классом Error, поэтому их допускается ловить через catch (Throwable $e).

Полный список констант с кодами ошибок

Встроенные константы находятся в массиве Core. Их можно вывести с помощью get_defined_constants.

Пример
$constants = get_defined_constants(true);
$errorConstants = $constants['Core'] ?? [];

foreach ($errorConstants as $name => $value) {
    if (strpos($name, 'E_') === 0) {
        echo $name . ' = ' . $value . PHP_EOL;
    }
}

Результат (фрагмент для PHP 8):

E_ERROR = 1
E_WARNING = 2
E_PARSE = 4
E_NOTICE = 8
E_CORE_ERROR = 16
E_CORE_WARNING = 32
E_COMPILE_ERROR = 64
E_COMPILE_WARNING = 128
E_USER_ERROR = 256
E_USER_WARNING = 512
E_USER_NOTICE = 1024
E_STRICT = 2048
E_RECOVERABLE_ERROR = 4096
E_DEPRECATED = 8192
E_USER_DEPRECATED = 16384
E_ALL = 32767

В разных версиях PHP значения одинаковы, но список может дополняться новыми константами.

Определение имени константы по числовому коду

Для вывода читаемого имени вместо числа применяется поиск по массиву констант.

Пример
function getErrorName(int $code): string {
    $constants = get_defined_constants(true);
    $candidates = [];
    foreach ($constants as $group) {
        foreach ($group as $name => $value) {
            if (strpos($name, 'E_') === 0) {
                $candidates[$name] = $value;
            }
        }
    }
    $name = array_search($code, $candidates, true);
    return $name !== false ? $name : 'UNKNOWN';
}

echo getErrorName(E_USER_ERROR) . PHP_EOL;
echo getErrorName(8) . PHP_EOL;
echo getErrorName(999999) . PHP_EOL;
E_USER_ERROR
E_NOTICE
UNKNOWN

Функция перебирает все группы констант, включая Zend и other. Такой подход полезен в обработчиках ошибок, где код выводится в лог вместе с понятным именем.

Превращение предупреждения в ErrorException

Класс ErrorException содержит поле code, в которое помещается код уровня ошибки. Обработчик, приведённый ниже, перехватывает warnings и notices.

Пример
function errorToException($severity, $message, $file, $line) {
    if (!(error_reporting() & $severity)) {
        return false;
    }
    throw new ErrorException($message, $severity, $severity, $file, $line);
}
set_error_handler('errorToException');

try {
    include 'missing-file.php';
} catch (ErrorException $e) {
    echo 'Ошибка с кодом ' . $e->getCode() . ' в файле ' . $e->getFile() . ':' . $e->getLine();
}

include несуществующего файла вызывает E_WARNING, поэтому catch получает ErrorException. Код ошибки будет 2. В файле, где нет прав на чтение, может возникнуть E_WARNING с тем же кодом 2.

Результат работы скрипта будет зависеть от имени файла и прав доступа:

Ошибка с кодом 2 в файле /var/www/missing-file.php:3

Важно: не путать код уровня ошибки (2) с кодом HTTP-ответа. Это разные вещи.

Логирование всех пользовательских ошибок с датой и временем

Пример
function logError($severity, $message, $file, $line) {
    $codeName = getErrorName($severity);
    $lineText = sprintf('[%s] %s (code %d): %s in %s on line %d', date('c'), $codeName, $severity, $message, $file, $line) . PHP_EOL;
    file_put_contents(__DIR__ . '/errors.log', $lineText, FILE_APPEND | LOCK_EX);
}

set_error_handler('logError');
trigger_error('Пользовательская заметка', E_USER_NOTICE);
trigger_error('Предупреждение в функции', E_USER_WARNING);

Содержимое errors.log после двух вызовов:

[2025-04-01T12:30:45+00:00] E_USER_NOTICE (code 1024): Пользовательская заметка in /var/www/log.php on line 10
[2025-04-01T12:30:45+00:00] E_USER_WARNING (code 512): Предупреждение в функции in /var/www/log.php on line 11

Обработчик не прекращает выполнение скрипта и позволяет сохранить оригинальное сообщение. Путь к файлу журнала лучше заменить на абсолютный путь, чтобы запись работала независимо от текущей директории.

Битовые операции с масками для отбора кодов

Так как коды ошибок являются степенью двойки, маска составляется через побитовые И, ИЛИ и NOT.

Пример
$allExceptNotice = E_ALL & ~E_NOTICE;
$warningAndUser = E_WARNING | E_USER_WARNING;
$withoutDeprecated = E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED;

echo $allExceptNotice . PHP_EOL;
echo $warningAndUser . PHP_EOL;
echo $withoutDeprecated . PHP_EOL;
32759
514
8191

Значения могут меняться, если в версии PHP добавлены новые биты. Поэтому в прикладном коде лучше использовать константы, а не числа.

Запуск PHP из командной строки с включёнными ошибками

Пример
php -d error_reporting=E_ALL -d display_errors=1 -r "trigger_error('Тест', E_USER_WARNING);"
PHP Warning:  Тест in Command line code on line 1

Использование кода исключения в catch

Пример
try {
    throw new InvalidArgumentException('Некорректный параметр', 422);
} catch (Throwable $e) {
    echo 'Тип: ' . get_class($e) . PHP_EOL;
    echo 'Код: ' . $e->getCode() . PHP_EOL;
    echo 'Сообщение: ' . $e->getMessage();
}
Тип: InvalidArgumentException
Код: 422
Сообщение: Некорректный параметр

Код исключения задаётся вторым аргументом конструктора. Его можно использовать для связи с внешними системами, не путая с уровнем ошибки PHP.

Обработка фатальной ошибки с уникальным идентификатором

Для критических кодов E_ERROR, E_PARSE и похожих применяется shutdown-перехват. Добавление уникального идентификатора помогает найти событие в общем журнале.

Пример
register_shutdown_function(function () {
    $last = error_get_last();
    if ($last && in_array($last['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR], true)) {
        $errorId = uniqid('fatal_', true);
        $text = sprintf('[%s] %s: %s in %s:%d', $errorId, $last['type'], $last['message'], $last['file'], $last['line']);
        file_put_contents(__DIR__ . '/fatal.log', $text . PHP_EOL, FILE_APPEND | LOCK_EX);
        echo 'Фатальная ошибка. Событие: ' . $errorId;
    }
});

function triggerFatal() {
    require '/missing/file.php';
}
triggerFatal();

Результат выполнения будет содержать идентификатор события:

Фатальная ошибка. Событие: fatal_65f0ab2c0b6c6

В файле fatal.log появится строка с кодом и сообщением. Идентификатор можно передать пользователю, чтобы потом найти причину без раскрытия внутренних данных.

Код ошибки PHP - comments

En
Php код ошибки (php)