Обработка ошибок в 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. Лучшие практики и частые ошибки
Правильная обработка ошибок — это не только синтаксис, но и архитектура. Вот несколько рекомендаций:
- Не злоупотребляйте try/catch — не оборачивайте каждую строчку. Исключения должны обрабатываться на уровне, где вы можете принять решение (повторить запрос, показать ошибку пользователю, записать в лог).
- Всегда логируйте исключения — используйте
error_log()или PSR-3 совместимые библиотеки (Monolog). Необработанные ошибки должны быть зафиксированы. - Не используйте исключения для управления потоком — если можно проверить условие (например,
file_exists()), сделайте это. Исключения — для исключительных ситуаций. - Указывайте типы исключений в catch — избегайте пустого
catch (Exception $e)без указания класса. Это может скрыть критические ошибки. - Создавайте иерархию исключений