Настройка собственного обработчика ошибок в PHP
Основные подходы к установке обработчика ошибок PHP
Функция set_error_handler() позволяет заменить стандартный обработчик ошибок PHP на пользовательский. Это даёт возможность централизованно логировать ошибки, выводить собственные сообщения или преобразовывать их в исключения. Обработчик не перехватывает фатальные ошибки (E_ERROR, E_PARSE и т.п.) – для них требуется дополнительная настройка.
Регистрация пользовательского обработчика с помощью callback-функции
Наиболее распространённый и эффективный способ – передать callable в set_error_handler(). Callback принимает до пяти аргументов: уровень ошибки, сообщение, файл, строку и контекст.
function myErrorHandler($severity, $message, $file, $line) {
// Логирование или другое действие
error_log("Ошибка [$severity]: $message в файле $file на строке $line");
// Возврат true означает, что ошибка обработана и стандартный вывод подавлен
return true;
}
// Установка обработчика для всех типов ошибок, кроме фатальных
set_error_handler("myErrorHandler");
// Пример вызова: вызов несуществующей функции
// trigger_error("Пример пользовательской ошибки", E_USER_WARNING);
Php set error (установка обработчика ошибок php)
Цель использования – перехват всех нефатальных ошибок (E_WARNING, E_NOTICE, E_USER_* и др.) для логирования или кастомизации поведения. Случаи применения: продакшн-среда (скрытие стандартных сообщений), отладка, интеграция с внешними системами.
Возможные проблемы:
- Фатальные ошибки (E_ERROR) не попадают в обработчик – их нужно обрабатывать отдельно через register_shutdown_function().
- Оператор подавления ошибок @ временно устанавливает уровень error_reporting в 0, что может блокировать вызов обработчика для данной ошибки. Для принудительного вызова нужно проверить значение error_reporting.
- Если внутри обработчика возникает новая ошибка, возможен бесконечный рекурсивный вызов. Рекомендуется переопределить обработчик на null перед выполнением опасных операций.
Как установить обработчик ошибок анонимной функцией (замыканием)?
Анонимные функции удобны, когда обработчик используется однократно или требует доступа к переменным контекста.
$logFile = 'errors.log';
set_error_handler(function($severity, $message, $file, $line) use ($logFile) {
$entry = "[" . date('Y-m-d H:i:s') . "] Ошибка уровня $severity: $message в $file:$line" . PHP_EOL;
file_put_contents($logFile, $entry, FILE_APPEND);
return true;
});
// Использование:
echo $undefinedVar; // Notice будет записан в errors.log
Цель – простой и компактный код без создания глобальных функций. Проблема: замыкание нельзя повторно отключить через restore_error_handler() по имени, только по очереди стека.
Как задать обработчик ошибок в объектно-ориентированном стиле?
Методы классов (статические или экземпляра) удобны для инкапсуляции логики обработки.
class ErrorLogger {
public static function handler($severity, $message, $file, $line) {
if (error_reporting() & $severity) { // уважаем error_reporting
echo "[LOG] Ошибка: $message";
}
return true;
}
}
set_error_handler(['ErrorLogger', 'handler']);
// Или через объект:
$logger = new ErrorLogger();
set_error_handler([$logger, 'handler']);
Случаи применения: классы-логгеры, фреймворки. Проблема: при передаче объекта нужно убедиться, что метод доступен и не удалён из памяти.
Как вернуть исходный обработчик после временной замены?
Функция restore_error_handler() восстанавливает предыдущий обработчик из стека (аналогично restore_exception_handler).
// Временная замена обработчика
set_error_handler(function($s, $m) { /* ... */ });
// ... выполнение кода ...
// Возврат к предыдущему обработчику
restore_error_handler();
Цель – временная смена поведения (например, для парсинга кода, где ошибки ожидаемы). Проблема: если несколько set_error_handler вызывались, стек раскручивается последовательно.
Как перехватывать фатальные ошибки, которые не попадают в set_error_handler?
Фатальные ошибки (E_ERROR, E_CORE_ERROR и т.п.) обрабатываются только через register_shutdown_function() в сочетании с error_get_last().
register_shutdown_function(function() {
$lastError = error_get_last();
if ($lastError && ($lastError['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR))) {
// Обработка фатальной ошибки
error_log("Фатальная ошибка: " . $lastError['message']);
}
});
// Пример: вызов неопределённой функции (E_ERROR)
// undefinedFunction();
Цель – перехват критических сбоев для логирования или корректного завершения. Проблема: shutdown-функция вызывается в любом случае, нужно проверять тип ошибки.
Как преобразовывать ошибки PHP в исключения (ErrorException)?
Это позволяет использовать блоки try/catch для обработки ошибок, которые обычно не являются исключениями.
function errorToException($severity, $message, $file, $line) {
throw new ErrorException($message, 0, $severity, $file, $line);
}
set_error_handler('errorToException');
try {
// Код, который может вызвать Warning или Notice
strpos(); // Неправильные аргументы
} catch (ErrorException $e) {
echo "Перехвачена ошибка: " . $e->getMessage();
}
Цель – единообразная обработка исключений и ошибок. Случаи: библиотеки, где ожидаются исключения. Проблема: не-фундаментальные ошибки (E_STRICT, E_DEPRECATED) тоже становятся исключениями – это может нарушить работу старого кода.
Развёрнутые примеры использования обработчика ошибок
Пример 1: Обработка различных уровней ошибок с логированием в файл
// Файл: error_handler.php
function universalHandler($severity, $message, $file, $line) {
$levelNames = [
E_WARNING => 'Warning',
E_NOTICE => 'Notice',
E_USER_WARNING => 'User Warning',
E_USER_NOTICE => 'User Notice',
E_STRICT => 'Strict',
E_DEPRECATED => 'Deprecated',
];
$levelName = $levelNames[$severity] ?? 'Unknown';
$entry = sprintf(
"[%s] %s: %s in %s:%d\n",
date('Y-m-d H:i:s'),
$levelName,
$message,
$file,
$line
);
file_put_contents('site_errors.log', $entry, FILE_APPEND);
// Не прерываем выполнение для Notices и Warnings
if ($severity === E_WARNING || $severity === E_USER_WARNING) {
// Можно показать сообщение пользователю
echo "Возникла проблема, запись добавлена в лог.";
}
return true;
}
set_error_handler('universalHandler');
// Вызовы:
trigger_error('Тестовое уведомление', E_USER_NOTICE);
trigger_error('Тестовое предупреждение', E_USER_WARNING);
$a = 1 / 0; // Division by zero (E_WARNING)
(Файл site_errors.log): [2025-04-15 10:00:00] User Notice: Тестовое уведомление in /path/to/test.php:12 [2025-04-15 10:00:01] User Warning: Тестовое предупреждение in /path/to/test.php:13 [2025-04-15 10:00:01] Warning: Division by zero in /path/to/test.php:14
Пример 2: Класс-обработчик с гибкими настройками
class ErrorHandler {
private $logDir;
private $displayErrors;
public function __construct($logDir = '/tmp', $displayErrors = false) {
$this->logDir = rtrim($logDir, '/');
$this->displayErrors = $displayErrors;
}
public function handle($severity, $message, $file, $line) {
$this->writeLog($severity, $message, $file, $line);
if ($this->displayErrors && (error_reporting() & $severity)) {
echo "Ошибка: " . htmlspecialchars($message);
}
// Не блокируем выполнение
return true;
}
private function writeLog($severity, $message, $file, $line) {
$logFile = $this->logDir . '/php_errors.log';
$entry = sprintf("[%s] [%d] %s: %s\n", date('c'), $severity, basename($file) . ':' . $line, $message);
file_put_contents($logFile, $entry, FILE_APPEND);
}
}
$handler = new ErrorHandler(__DIR__ . '/logs', true);
set_error_handler([$handler, 'handle']);
// Проверка
file_get_contents('/nonexistent'); // Warning
Вывод на экране: Ошибка: file_get_contents(/nonexistent): Failed to open stream: No such file or directory (в логе аналогичная запись)
Пример 3: Преобразование ошибок в исключения с восстановлением после try-catch
set_error_handler(function($severity, $message, $file, $line) {
if (error_reporting() & $severity) {
throw new ErrorException($message, 0, $severity, $file, $line);
}
return false; // разрешаем стандартное поведение для подавленных ошибок
});
function riskyOperation($value) {
// Предположим, что $value может быть нечисловым
return 100 / $value;
}
try {
$result = riskyOperation(0);
echo "Результат: $result";
} catch (ErrorException $e) {
echo "Поймана ошибка: " . $e->getMessage() . " (код: " . $e->getCode() . ")";
// Действия по восстановлению
$result = 0;
}
echo "\nПродолжение выполнения...";
Поймана ошибка: Division by zero (код: 2) Продолжение выполнения...
Пример 4: Обработка фатальных ошибок с помощью shutdown-функции
register_shutdown_function(function() {
$error = error_get_last();
if ($error && ($error['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR))) {
echo "Критическая ошибка: " . $error['message'] . " в файле " . $error['file'] . " на строке " . $error['line'];
// Здесь можно отправить email разработчику или записать в лог
error_log("FATAL: " . json_encode($error));
}
});
// Вызов несуществующего класса - E_ERROR
// $obj = new NonExistentClass();
Критическая ошибка: Class "NonExistentClass" not found в файле /path/to/test.php на строке 10 (В логе ошибок аналогичная запись)
Пример 5: Влияние оператора подавления ошибок (@) на обработчик
set_error_handler(function($severity, $message) {
echo "Обработчик вызван для: $message\n";
return true;
});
// Без @
file_get_contents('/missing'); // Warning, обработчик сработает
// С @
@file_get_contents('/missing'); // Warning скрыто, обработчик НЕ будет вызван
Обработчик вызван для: file_get_contents(/missing): Failed to open stream: No such file or directory (второй раз сообщение не выводится)
Это происходит потому, что оператор @ временно устанавливает error_reporting(0). Чтобы обработчик всё равно вызывался, внутри него можно проверить значение error_reporting() и, если оно равно 0, самостоятельно решить, обрабатывать ли ошибку.