Обработка фатальных ошибок PHP: как избежать краха приложения

Раздел: Отладка PHP -> Обработка ошибок и исключений PHP

Понимание и устранение фатальной ошибки необработанного исключения класса PHP

Как правильно перехватывать необработанные исключения класса Error, чтобы избежать фатальной остановки скрипта?

Наиболее эффективный способ - сочетание локального перехвата с блоком try-catch и глобального обработчика через set_exception_handler. Это позволяет обрабатывать как ожидаемые, так и неожиданные ошибки, сохраняя контроль над выполнением.


// Глобальный обработчик неперехваченных исключений (включая Error)
set_exception_handler(function($exception) {
    // Логирование в файл
    error_log("Необработанное исключение: " . $exception->getMessage() . " в " . $exception->getFile() . ":" . $exception->getLine());
    // Отображение пользователю (в production лучше скрыть)
    if (ini_get('display_errors')) {
        echo "Произошла критическая ошибка. Пожалуйста, попробуйте позже.";
    }
});

// Пример кода с потенциальной ошибкой
function divide($a, $b) {
    if ($b === 0) {
        throw new \DivisionByZeroError('Деление на ноль');
    }
    return $a / $b;
}

try {
    echo divide(10, 0);
} catch (\DivisionByZeroError $e) {
    echo 'Обработано локально: ' . $e->getMessage();
}
  

Возможные проблемы:

  • Если исключение выброшено внутри деструктора или в callback-функции, глобальный обработчик может не сработать.
  • Не следует полагаться только на глобальный обработчик для критических операций - всегда нужно оборачивать рискованный код в try-catch.

Цель: централизованная обработка всех неперехваченных исключений и ошибок класса Error, предотвращение белого экрана смерти (WSOD). Случаи использования: production-среда, где необходима запись ошибок в лог и вывод понятного сообщения пользователю.

Вариант 1. Как перехватить конкретный тип ошибки (например, TypeError или ValueError)?

Используется блок try-catch с указанием класса ошибки. Позволяет обрабатывать только определённые ситуации, не затрагивая другие исключения.


try {
    // Функция ожидает int, но получает string
    $result = array_sum(['a', 'b']); // TypeError
} catch (\TypeError $e) {
    echo 'Тип данных неверен: ' . $e->getMessage();
}
  

Типичные ошибки:

  • Перехват родительского класса \Error вместо конкретного - теряется возможность точной диагностики.
  • Забывают, что ошибки наследуются: \DivisionByZeroError extends \ArithmeticError extends \Error.

Цель: точная реакция на определённые типы фатальных ошибок (неверный тип, деление на ноль, переполнение). Используется в библиотеках и компонентах, где известны возможные сценарии сбоев.

Вариант 2. Как использовать глобальный обработчик для всех неперехваченных исключений (включая Error)?

Функция set_exception_handler перехватывает любые исключения, не пойманные в try-catch. Для ошибок класса Error это единственный способ предотвратить фатальное завершение.


set_exception_handler(function($e) {
    // Логирование в системный лог
    error_log($e);
    // Вывод страницы с ошибкой
    http_response_code(500);
    include 'error_page.php';
});

// Пример: вызов несуществующего метода
$obj = new stdClass();
$obj->nonExistentMethod(); // Error: Call to undefined method
  

Проблемы:

  • Нельзя восстановить выполнение после обработчика - скрипт всё равно завершится.
  • Внутри обработчика нельзя выбрасывать новое исключение - это приведёт к повторному вызову.

Цель: единая точка входа для логирования и отображения ошибок в production. Случаи использования: фреймворки, CMS, где нужно стандартизировать вывод ошибок.

Вариант 3. Как преобразовывать фатальные ошибки в исключения с помощью set_error_handler?

Для нефатальных ошибок (Notice, Warning) set_error_handler позволяет выбросить ErrorException. Однако класс Error (фатальные) не проходит через этот обработчик - для них нужен отдельный механизм (например, register_shutdown_function).


set_error_handler(function($severity, $message, $file, $line) {
    if (!(error_reporting() & $severity)) {
        // Ошибка подавлена оператором @
        return false;
    }
    throw new \ErrorException($message, 0, $severity, $file, $line);
});

// Пример: Warning от file_get_contents
set_error_handler(null); // Временно отключаем для примера
  

Типичные ошибки:

  • Путаница между фатальными (Error) и нефатальными ошибками. set_error_handler не ловит Error.
  • Использование @ для подавления ошибок не рекомендуется, но если оно используется, обработчик не срабатывает.

Цель: унифицировать обработку всех ошибок как исключений (для Warning, Notice). Не подходит для фатальных Error, но полезно в комбинации с другими методами.

Вариант 4. Как логировать фатальные ошибки с 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']}");
        // Отправляем уведомление администратору
        mail('admin@example.com', 'Фатальная ошибка', print_r($error, true));
    }
});
  

Проблемы:

  • Нельзя повлиять на выполнение скрипта - он уже завершается.
  • Если произошла несколько фатальных ошибок, фиксируется только последняя.

Цель: гарантированное логирование любых фатальных ошибок, даже тех, которые не были перехвачены try-catch. Используется как дополнение к основному обработчику.

Вариант 5. Как настроить отображение ошибок через php.ini для отладки?

Параметры display_errors, error_reporting и log_errors управляют выводом и записью ошибок. Для фатальных Error это даёт полную информацию на этапе разработки.


; php.ini (development)
display_errors = On
error_reporting = E_ALL
log_errors = On
error_log = /var/log/php_errors.log
  

Типичные ошибки:

  • В production оставляют display_errors = On - это раскрывает чувствительные данные.
  • Не настраивают rotate логов, что приводит к переполнению диска.

Цель: быстрая отладка в разработке и централизованное логирование в production. Не является методом обработки, но обязательная часть инфраструктуры.

Вариант 6. Как использовать Xdebug для трассировки фатальных ошибок?

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


; php.ini с Xdebug
zend_extension=xdebug.so
xdebug.mode=develop
xdebug.collect_params=4
  

Проблемы:

  • Xdebug замедляет выполнение скрипта, не рекомендуется для production.
  • Требуется установка и настройка сервера.

Цель: глубокая отладка во время разработки, особенно для сложных ошибок, где нужно видеть цепочку вызовов.

Расширенные примеры обработки фатальных ошибок класса Error

Пример

// Пример 1: Перехват TypeError с использованием union типов (PHP 8+)
declare(strict_types=1);

function processString(string $input): string {
    return strtoupper($input);
}

$values = ["hello", 123, null];
foreach ($values as $val) {
    try {
        echo processString($val) . "\n";
    } catch (\TypeError $e) {
        echo "TypeError: " . $e->getMessage() . "\n";
    }
}
HELLO
TypeError: processString(): Argument #1 ($input) must be of type string, int given, called in ... on line ...
TypeError: processString(): Argument #1 ($input) must be of type string, null given, called in ... on line ...
Пример

// Пример 2: Комбинированный перехват нескольких классов Error
try {
    // Вызов несуществующего метода
    $obj = new stdClass();
    $obj->method();
} catch (\Error $e) {
    // catch-all для любого Error
    echo "Общий перехват Error: " . $e->getMessage() . "\n";
    // Проверка на конкретный тип
    if ($e instanceof \Error) {
        // Дополнительная обработка
    }
}
Общий перехват Error: Call to undefined method stdClass::method()
Пример

// Пример 3: Использование set_exception_handler с разными сценариями
set_exception_handler(function(\Throwable $e) {
    // Логирование с дополнительной информацией о запросе
    $context = [
        'message' => $e->getMessage(),
        'file'    => $e->getFile(),
        'line'    => $e->getLine(),
        'url'     => $_SERVER['REQUEST_URI'] ?? 'cli',
        'method'  => $_SERVER['REQUEST_METHOD'] ?? 'cli',
    ];
    error_log(json_encode($context));
    // Отправка в Sentry/Bugsnag
    // \Raven_Client::captureException($e);
});

// Генерация ParseError с помощью eval
eval('invalid php code');
(В логе появится запись, скрипт завершится с кодом 500)
Пример

// Пример 4: register_shutdown_function + фильтрация ошибок
register_shutdown_function(function() {
    $lastError = error_get_last();
    if ($lastError && ($lastError['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR))) {
        // Сохраняем в базу данных
        $db = new PDO('mysql:host=localhost;dbname=errors', 'user', 'pass');
        $stmt = $db->prepare('INSERT INTO fatal_errors (message, file, line, time) VALUES (?, ?, ?, NOW())');
        $stmt->execute([$lastError['message'], $lastError['file'], $lastError['line']]);
    }
});
Пример

// Пример 5: Преобразование Warning в исключение с сохранением стека
set_error_handler(function($severity, $message, $file, $line, $context) {
    if (error_reporting() & $severity) {
        $e = new \ErrorException($message, 0, $severity, $file, $line);
        $e->context = $context; // прикрепляем контекст
        throw $e;
    }
    return true;
});

try {
    $fp = fopen('/nonexistent.txt', 'r');
} catch (\ErrorException $e) {
    echo "Поймано исключение: " . $e->getMessage() . " (код: {$e->getSeverity()})\n";
}
Поймано исключение: fopen(/nonexistent.txt): Failed to open stream: No such file or directory (код: 2)
Пример

// Пример 6: Использование AssertError (PHP 7+) с пользовательским сообщением
ini_set('zend.assertions', 1);
ini_set('assert.exception', 1);

$value = 5;
try {
    assert($value > 10, 'Значение должно быть больше 10');
} catch (\AssertionError $e) {
    echo "Ошибка утверждения: " . $e->getMessage() . "\n";
}
Ошибка утверждения: Значение должно быть больше 10
- Php fatal error uncaught error class (фатальная ошибка необработанного исключения класса php)

Фатальная ошибка необработанного исключения класса PHP - comments

En
Php fatal error uncaught error class (php)