Исключения и ошибки
Исключение — это специальный объект, который является экземпляром встроенного класса Exceptionили производного от него класса. Объекты типа Exception предназначены для хранения информации об ошибках и выдачи сообщений о них.
Вместе с объектом типа Exception используется инструкция с ключевым словом throw. Эта инструкция останавливает выполнение текущего метода и передает ответственность за обработку ошибок обратно вызывающему коду.
Пример
Пример обработки Exceptions в коде
Ошибки
Когда исключения только внедрялись, механизмы try-catch применялись главным образом в коде сценариев, но не в самом ядре РНР , где возникавшие ошибки обрабатывались собственной логикой. Это могло привести к серьезным осложнениям, если требовалось обрабатывать ошибки, возникающие в ядре РНР , таким же образом, как и исключения в прикладном коде. В качестве первоначальной меры борьбы с подобными осложнениями в версии РНР 7 был создан внутренний класс Error, в котором реализован тот же встроенный интерфейс Throwable, что и в классе Exception. Поэтому с классом Error можно обращаться так же, как и с классом Exception.
Практика
1. Создание пользовательского исключения для валидации данных
Создайте пользовательское исключение для валидации email-адресов. Например, если email-адрес не соответствует стандартному формату, выбрасывается исключение с соответствующим сообщением. Также добавьте обработку этого исключения, чтобы выводить на экран сообщение о недействительном email.
Задание:
- Создайте класс
InvalidEmailException
, который наследуется от встроенного классаException
. - Напишите функцию
validateEmail($email)
, которая будет проверять формат email-адреса с помощью регулярного выражения. Если формат некорректен — выбрасывайте исключениеInvalidEmailException
. - Обработайте исключение в основном коде и выведите сообщение об ошибке.
2. Логирование ошибок в файл
Реализуйте систему логирования ошибок в файл. При возникновении исключения, создайте лог-файл, куда будет записываться информация о возникшей ошибке, включая дату, время, сообщение и стек вызовов.
Задание:
- Создайте класс
ErrorLogger
, который будет логировать ошибки в файл (например,error_log.txt
). - Используйте метод
set_exception_handler
для обработки необработанных исключений, и записывайте информацию об ошибке в файл. - Реализуйте логику, при которой в лог-файл будет записываться ошибка с полным стеком вызовов и временной меткой.
3. Создание исключений для работы с пользовательскими типами данных
Разработайте кастомный тип исключений для работы с ограничениями по возрасту пользователя. Например, если возраст пользователя меньше 18 лет, выбрасывайте исключение с определенным сообщением.
Задание:
- Создайте класс
UnderageException
, наследующий отException
. - Напишите функцию
checkAge($age)
, которая будет проверять, что возраст пользователя больше или равен 18. Если возраст меньше 18 — выбрасывайте исключениеUnderageException
. - Обработайте исключение и выведите сообщение об ошибке.
4. Обработка нескольких типов исключений
Реализуйте код, в котором можно обработать несколько типов исключений, например, исключения на основе типа ошибки базы данных и ошибки валидации данных.
Задание:
- Создайте два класса исключений:
DatabaseException
иValidationException
. - Напишите функцию, которая может выбрасывать одно из этих исключений в зависимости от типа ошибки.
- В коде обработчика используйте конструкцию
try-catch
для обработки обоих типов исключений.
5. Реализация обработки исключений с использованием логирования и ретраев
Разработайте систему, которая будет повторно пытаться выполнить действие в случае ошибки, например, при временных проблемах с базой данных (с использованием исключений). При этом ошибки будут логироваться.
Задание:
- Создайте класс
RetryDatabaseException
, который будет выбрасываться в случае неудачных попыток подключения к базе данных. - Напишите функцию, которая будет пытаться подключиться к базе данных, но в случае ошибки будет пытаться подключиться несколько раз (до 3 попыток).
- Если все попытки неудачны, выбросите исключение
RetryDatabaseException
и запишите в лог информацию о неудачных попытках.
// Создание пользовательского исключения
// для валидации данных
// 1. Создайте класс `InvalidEmailException`,
// который наследуется от встроенного класса
// `Exception`.
class InvalidEmailException extends Exception {
}
function validateEmail(string $email) {
$pattern = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';
$result = preg_match($pattern, $email);
if (!$result) {
throw new InvalidEmailException('Почта: ' . $email . ', не является валидной');
}
return true;
}
validateEmail('sdfsdfsdf');
// Результат:
/**
*
* Fatal error: Uncaught InvalidEmailException: Почта: sdfsdfsdf,
* не является валидной in /var/www/www/php8-book/exceptions.php:19
* Stack trace: #0 /var/www/www/php8-book/exceptions.php(25):
* validateEmail('sdfsdfsdf') #1 {main} thrown in
* /var/www/www/php8-book/exceptions.php on line 19
*
*/
// Базовый класс для пользовательских исключений
class CustomException extends Exception {
// Дополнительные свойства
protected $context;
protected $logFile;
// Конструктор с возможностью передачи дополнительных параметров
public function __construct($message = "", $code = 0, Exception $previous = null, $context = [], $logFile = 'exception.log') {
$this->context = $context;
$this->logFile = $logFile;
// Вызываем конструктор родительского класса
parent::__construct($message, $code, $previous);
}
// Переопределение метода __toString()
public function __toString() {
// Формируем строковое представление ошибки с контекстом
$contextInfo = json_encode($this->context);
return "Exception: [{$this->code}] {$this->message} in {$this->file} on line {$this->line}\nContext: {$contextInfo}\n";
}
// Метод для записи ошибки в лог-файл
public function logError() {
$errorDetails = "Exception: [{$this->code}] {$this->message} in {$this->file} on line {$this->line}\n";
$errorDetails .= "Context: " . json_encode($this->context) . "\n";
$errorDetails .= "Stack Trace: " . $this->getTraceAsString() . "\n\n";
// Логирование ошибки в файл
file_put_contents($this->logFile, $errorDetails, FILE_APPEND);
}
// Переопределение метода для отображения пользовательского интерфейса ошибки
public function displayUserFriendlyMessage() {
return "An error occurred. Please contact support with the error code: {$this->code}.";
}
}
// Исключение для ошибок валидации
class ValidationException extends CustomException {
// Дополнительное поле для полей, которые не прошли валидацию
protected $invalidFields;
public function __construct($message = "", $code = 0, $invalidFields = [], $context = [], $logFile = 'validation_errors.log') {
$this->invalidFields = $invalidFields;
parent::__construct($message, $code, null, $context, $logFile);
}
// Переопределение __toString() для отображения деталей полей
public function __toString() {
$invalidFields = implode(', ', $this->invalidFields);
return parent::__toString() . "Invalid Fields: {$invalidFields}\n";
}
public function getInvalidFields() {
return $this->invalidFields;
}
}
// Исключение для ошибок работы с БД
class DatabaseException extends CustomException {
protected $query;
protected $dbError;
public function __construct($message = "", $code = 0, $query = "", $dbError = "", $context = [], $logFile = 'db_errors.log') {
$this->query = $query;
$this->dbError = $dbError;
parent::__construct($message, $code, null, $context, $logFile);
}
public function __toString() {
return parent::__toString() . "SQL Query: {$this->query}\nDB Error: {$this->dbError}\n";
}
}
// Пример использования исключений
try {
// Симулируем ошибку валидации
$data = ['username' => '', 'email' => 'invalidemail'];
$invalidFields = [];
// Проверка на пустое поле username
if (empty($data['username'])) {
$invalidFields[] = 'username';
}
// Проверка на неправильный email
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
$invalidFields[] = 'email';
}
if (!empty($invalidFields)) {
throw new ValidationException(
"Validation failed for some fields.",
1001,
$invalidFields,
['data' => $data]
);
}
// Симулируем ошибку работы с БД
throw new DatabaseException(
"Database connection failed.",
2001,
"SELECT * FROM users WHERE id = 1",
"SQLSTATE[HY000] [1049] Unknown database 'nonexistent_db'",
['server' => 'localhost', 'db' => 'nonexistent_db']
);
} catch (ValidationException $e) {
// Логируем ошибку
$e->logError();
// Показываем пользовательское сообщение
echo $e->displayUserFriendlyMessage();
// Отображаем стек вызова
echo $e->__toString();
} catch (DatabaseException $e) {
// Логируем ошибку
$e->logError();
// Показываем пользовательское сообщение
echo $e->displayUserFriendlyMessage();
// Отображаем стек вызова
echo $e->__toString();
} catch (CustomException $e) {
// Логируем ошибку
$e->logError();
// Показываем сообщение об ошибке
echo $e->displayUserFriendlyMessage();
// Отображаем стек вызова
echo $e->__toString();
}