Обработка ошибок в PHP: try/catch и не только — полное руководство

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

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

Начать курс

Обработка ошибок в PHP: от базовых конструкций до профессиональных паттернов

Любой PHP-разработчик рано или поздно сталкивается с ситуацией, когда код ведёт себя не так, как ожидалось. Файл не найден, база данных недоступна, пользователь ввёл некорректные данные — всё это исключительные ситуации. Грамотная обработка ошибок превращает хрупкое приложение в надёжную систему. В этой статье мы разберём все аспекты работы с ошибками в PHP: от простого try/catch до создания собственных классов исключений и глобальных обработчиков.

1. Основы: конструкция try/catch и throw

В PHP исключения — это объекты класса Exception (или его наследников). Базовый механизм состоит из трёх ключевых слов:

  • try — блок, в котором выполняется потенциально опасный код;
  • catch — блок, перехватывающий исключение;
  • throw — оператор, который «выбрасывает» исключение.

Рассмотрим простейший пример:

<?php
function divide($a, $b) {
    if ($b == 0) {
        throw new Exception('Деление на ноль невозможно');
    }
    return $a / $b;
}

try {
    echo divide(10, 0);
} catch (Exception $e) {
    echo 'Ошибка: ' . $e->getMessage();
}
?>

Когда выполнение доходит до throw, PHP немедленно покидает текущую функцию и ищет подходящий блок catch. Если catch найден — выполнение продолжается с него. Если нет — возникает фатальная ошибка.

Важно: начиная с PHP 7, большинство внутренних ошибок (например, деление на ноль) стали выбрасывать исключения класса DivisionByZeroError. Для их перехвата нужно указывать соответствующий тип.

2. Расширенные возможности: множественные catch и finally

Часто в одном блоке try могут возникать разные типы исключений. PHP позволяет создавать несколько блоков catch для разных классов:

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

try {
    // код, который может выбросить разные исключения
} catch (ValidationException $e) {
    echo 'Ошибка валидации: ' . $e->getMessage();
} catch (DatabaseException $e) {
    echo 'Ошибка базы данных: ' . $e->getMessage();
} catch (Exception $e) {
    // Общий обработчик для всех остальных исключений
    echo 'Неизвестная ошибка: ' . $e->getMessage();
}
?>

Порядок блоков catch имеет значение: PHP проверяет их последовательно, и первый подходящий по типу перехватывает исключение. Поэтому общий catch (Exception $e) всегда ставят последним.

Блок finally выполняется в любом случае — и после успешного завершения try, и после перехвата исключения, и даже если в catch был оператор return. Это идеальное место для закрытия файлов, освобождения ресурсов или записи в лог:

<?php
function readConfig($filename) {
    $handle = fopen($filename, 'r');
    if (!$handle) {
        throw new Exception('Не удалось открыть файл');
    }
    try {
        // чтение данных
        return fread($handle, filesize($filename));
    } finally {
        fclose($handle); // файл закроется даже при исключении
    }
}
?>

3. Пользовательские классы исключений и глобальные обработчики

Создание собственных классов исключений — мощный инструмент для структурирования кода. Достаточно унаследоваться от Exception:

<?php
class InvalidEmailException extends Exception {
    public function __construct($email, $code = 0, Throwable $previous = null) {
        $message = "Email '$email' имеет неверный формат";
        parent::__construct($message, $code, $previous);
    }
}

function validateEmail($email) {
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        throw new InvalidEmailException($email);
    }
    return true;
}

try {
    validateEmail('not-an-email');
} catch (InvalidEmailException $e) {
    echo 'Пользовательская ошибка: ' . $e->getMessage();
}
?>

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

<?php
set_exception_handler(function($exception) {
    // Запись в лог-файл
    error_log("Необработанное исключение: " . $exception->getMessage());
    // Отображение дружественной страницы ошибки
    http_response_code(500);
    echo "<h1>Внутренняя ошибка сервера</h1>";
    echo "<p>Пожалуйста, попробуйте позже.</p>";
    exit;
});

// Теперь любое необработанное исключение попадёт сюда
throw new Exception('Тестовое исключение');
?>

Дополнительно можно использовать set_error_handler() для перехвата стандартных ошибок PHP (E_WARNING, E_NOTICE) и преобразования их в исключения. Это особенно полезно при работе с устаревшим кодом.

4. Лучшие практики и частые ошибки

Правильная обработка ошибок — это не только синтаксис, но и архитектура. Вот несколько рекомендаций: