Как перехватить фатальную ошибку (E_ERROR) в PHP?
Проблема с перехватом E_ERROR в PHP
Вы правы: стандартный set_error_handler() не перехватывает фатальные ошибки (E_ERROR, E_PARSE, E_CORE_ERROR и т.д.), потому что они происходят на уровне выполнения, когда скрипт уже не может безопасно продолжать работу. Однако PHP предоставляет два основных инструмента для работы с такими ошибками: register_shutdown_function() и исключения Throwable (в PHP 7+).
Решение 1: Использование register_shutdown_function()
Этот метод регистрирует функцию, которая вызывается при завершении скрипта — как при успешном выполнении, так и при фатальной ошибке. Чтобы отличить фатальную ошибку от нормального завершения, используйте error_get_last():
register_shutdown_function(function() {
$error = error_get_last();
if ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
// Фатальная ошибка произошла
error_log("Фатальная ошибка: {$error['message']} в файле {$error['file']} на строке {$error['line']}");
echo "Произошла критическая ошибка. Пожалуйста, попробуйте позже.";
// Здесь можно отправить email администратору, записать в БД и т.д.
}
});
// Ваш проблемный код
function processData($data) {
$obj = new SomeClass();
$obj->nonExistentMethod(); // Вызовет E_ERROR
}
processData($userInput);
Важно: После фатальной ошибки скрипт всё равно завершится — вы не можете продолжить выполнение после E_ERROR. Но shutdown-функция даёт возможность корректно обработать ситуацию (залогировать, вывести сообщение пользователю).
Решение 2: Использование try-catch с Throwable (PHP 7+)
Начиная с PHP 7, большинство фатальных ошибок стали "исключаемыми" — они выбрасывают исключения, реализующие интерфейс Throwable. Например, вызов несуществующего метода теперь выбрасывает Error, который можно поймать:
try {
$obj = new SomeClass();
$obj->nonExistentMethod(); // Выбросит Error
} catch (\Throwable $e) {
// Ловим любые ошибки и исключения
error_log("Поймана ошибка: " . $e->getMessage());
echo "Что-то пошло не так. Мы уже работаем над этим.";
}
// Код продолжает выполняться после catch
echo "Этот текст будет выведен!";
Это работает для большинства ситуаций (вызов несуществующего метода, деление на ноль в некоторых контекстах и т.д.), но не для всех. Например, ошибки памяти (Allowed memory size exhausted) всё ещё могут быть не перехвачены.
Решение 3: Комбинированный подход (рекомендуется)
Лучшая практика — объединить оба метода:
// 1. Преобразуем предупреждения и заметки в исключения
set_error_handler(function($severity, $message, $file, $line) {
throw new ErrorException($message, 0, $severity, $file, $line);
});
// 2. Обрабатываем фатальные ошибки при завершении
register_shutdown_function(function() {
$error = error_get_last();
if ($error !== null && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
error_log("Неотлавливаемая ошибка: {$error['message']}");
echo "Критическая ошибка. Пожалуйста, свяжитесь с администратором.";
}
});
// 3. Основной код с try-catch
try {
// Ваш потенциально опасный код
processData($userInput);
} catch (\Throwable $e) {
// Ловим всё, что можно поймать
error_log("Обработанная ошибка: " . $e->getMessage());
echo "Произошла ошибка: " . htmlspecialchars($e->getMessage());
}
Важные замечания
- В production-режиме отключите вывод ошибок (
ini_set('display_errors', 0)) и включите логирование (ini_set('log_errors', 1)). - Фатальные ошибки, такие как
E_PARSE(синтаксические ошибки), не могут быть перехвачены ни одним из методов — скрипт не выполняется вообще. Проверяйте код через линтеры перед деплоем. - Для ошибок памяти используйте
memory_limitи мониторинг, а не попытки их перехватить — после такой ошибки состояние скрипта неопределённо.
Альтернативный подход: Использование глобального обработчика исключений
Вместо комбинирования нескольких функций вы можете использовать set_exception_handler() для обработки всех неперехваченных исключений (включая Error в PHP 7+). Это более чистый способ для приложений, где вы хотите централизованно обрабатывать все ошибки:
// Глобальный обработчик неперехваченных исключений
set_exception_handler(function(\Throwable $e) {
error_log("Необработанное исключение: " . $e->getMessage() . " в " . $e->getFile() . ":" . $e->getLine());
http_response_code(500);
echo "Внутренняя ошибка сервера. Пожалуйста, попробуйте позже.";
});
// Этот обработчик ловит:
// - Все обычные исключения (Exception)
// - Фатальные ошибки, которые стали исключениями (Error, TypeError, ParseError и т.д.)
// - Но НЕ ловит: ошибки памяти, таймауты выполнения
// Пример кода с ошибкой:
function riskyOperation() {
$arr = [];
$arr->nonExistentMethod(); // Выбросит Error
}
riskyOperation(); // Будет перехвачено set_exception_handler
echo "Этот код не выполнится, если ошибка не будет поймана в try-catch.";
Плюсы: Простота, централизованная обработка, работает с большинством современных фатальных ошибок.
Минусы: Не ловит ошибки, которые не стали исключениями (например, некоторые ошибки памяти), и скрипт всё равно завершается после обработчика.
Для максимальной надёжности комбинируйте set_exception_handler с register_shutdown_function, как описано в первом ответе.