Обработка ошибок в PHP: try/catch и продвинутые техники

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Почему базовая обработка ошибок — это только начало

Каждый PHP-разработчик знает конструкцию try/catch. Но когда проект вырастает до тысяч строк кода, а нагрузка на сервер становится высокой, простого перехвата исключений недостаточно. Продвинутая обработка ошибок — это не просто «поймал и забыл», а целая система: от логирования до трансформации предупреждений в исключения.

В этой статье мы разберем техники, которые помогут вам писать надёжный и отказоустойчивый код: множественные catch, пользовательские классы исключений, глобальные обработчики и интеграцию с PSR-3 логгерами. Материал рассчитан на разработчиков, уже знакомых с основами PHP.

1. Множественные блоки catch и иерархия исключений

Начиная с PHP 7.1, вы можете перехватывать разные типы исключений в одном блоке try. Это позволяет точечно реагировать на ошибки:

<?php
class DatabaseException extends \Exception {}
class ValidationException extends \Exception {}

try {
    // код, который может выбросить разные исключения
    $user = $db->query('SELECT ...');
    if (!$user) {
        throw new DatabaseException('Ошибка запроса');
    }
    if (empty($user['email'])) {
        throw new ValidationException('Email обязателен');
    }
} catch (DatabaseException $e) {
    // логируем ошибку БД
    error_log('DB Error: ' . $e->getMessage());
    echo 'Временная ошибка сервера. Попробуйте позже.';
} catch (ValidationException $e) {
    // показываем пользователю понятное сообщение
    echo 'Ошибка: ' . htmlspecialchars($e->getMessage());
} catch (\Throwable $e) {
    // ловим всё остальное (включая Error)
    error_log('Critical: ' . $e->getMessage());
    echo 'Критическая ошибка.';
}
?>

Почему это важно? Каждый тип исключения требует своей реакции. Ошибка БД — скрыть детали от пользователя, ошибка валидации — показать форму с подсветкой поля, критическая ошибка — отправить уведомление администратору.

Обратите внимание на \Throwable — это интерфейс, который реализуют и Exception, и Error (фатальные ошибки PHP). Используйте его в последнем блоке catch, чтобы не пропустить ни одной ошибки.

2. Пользовательские классы исключений: расширяем стандарты

Стандартный класс \Exception даёт базовый функционал. Но в реальных проектах удобно создавать свои иерархии исключений — это упрощает отладку и обработку.

Пример структуры для веб-приложения:

<?php
namespace App\Exceptions;

// Базовое исключение приложения
class AppException extends \RuntimeException
{
    protected $context = [];

    public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null, array $context = [])
    {
        parent::__construct($message, $code, $previous);
        $this->context = $context;
    }

    public function getContext(): array
    {
        return $this->context;
    }
}

// Исключение для HTTP-ошибок
class HttpException extends AppException
{
    private int $statusCode;

    public function __construct(int $statusCode, string $message = '', array $context = [])
    {
        $this->statusCode = $statusCode;
        parent::__construct($message, $statusCode, null, $context);
    }

    public function getStatusCode(): int
    {
        return $this->statusCode;
    }
}

// Использование
throw new HttpException(404, 'Страница не найдена', ['uri' => $_SERVER['REQUEST_URI']]);
?>

Преимущества:

  • Можно передавать дополнительный контекст (параметры запроса, ID пользователя) — это упрощает логирование.
  • Исключения группируются по смыслу: HttpException, ValidationException, PaymentException.
  • В блоке catch вы точно знаете, что делать с каждым типом.

3. Глобальный обработчик ошибок: set_error_handler и set_exception_handler

Не все ошибки в PHP — исключения. Предупреждения (E_WARNING) и уведомления (E_NOTICE) не выбрасывают исключений, но могут указывать на проблемы. С помощью set_error_handler() вы можете превратить любую ошибку в исключение:

<?php
// Превращаем все PHP-ошибки в исключения
set_error_handler(function (int $severity, string $message, string $file, int $line) {
    // Пропускаем ошибки, которые не входят в error_reporting
    if (!(error_reporting() & $severity)) {
        return false;
    }
    throw new \ErrorException($message, 0, $severity, $file, $line);
});

// А это — глобальный обработчик непойманных исключений
set_exception_handler(function (\Throwable $e) {
    // Логируем в файл
    $log = sprintf(
        "[%s] %s in %s:%d\n%s\n",
        date('Y-m-d H:i:s'),
        $e->getMessage(),
        $e->getFile(),
        $e->getLine(),
        $e->getTraceAsString()
    );
    file_put_contents(__DIR__ . '/../var/log/error.log', $log, FILE_APPEND);

    // Отдаём 500-ю страницу
    http_response_code(500);
    if (php_sapi_name() !== 'cli') {
        include __DIR__ . '/../templates/500.html';
    }
});

// Теперь любой Warning станет исключением:
try {
    $result = 1 / 0; // Division by zero
} catch (\ErrorException $e) {
    echo 'Поймали: ' . $e->getMessage();
}
?>

Важно: такой подход делает код единообразным — все ошибки обрабатываются через try/catch. Но будьте осторожны: не все ошибки нужно превращать в исключения. Например, E_DEPRECATED лучше просто логировать.

4. Логирование и интеграция с PSR-3

Хранить ошибки в текстовом файле — хорошо для отладки, но плохо для продакшена. Используйте PSR-3 совместимые логгеры (Monolog, Analog и т.д.). Это даёт гибкость: можно писать в файл, отправлять в Telegram/Slack или в ELK-стек.

Блоги

Рекомендаций по книгам