Введение: Почему базовая обработка ошибок — это только начало
Каждый 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-стек.
Блоги
Рекомендаций по книгам
Читайте также
Как начать изучать Python с нуля: лучшая стратегия + 17 шагов для самообучения