Конструкция try catch в языке PHP для обработки ошибок
Обработка исключений с помощью try catch в PHP
Основной синтаксис блока try catch предполагает заключение потенциально опасного кода в try и определение одного или нескольких catch для перехвата исключений.
<?php
try {
$number = 10 / 0;
} catch (DivisionByZeroError $e) {
echo 'Ошибка: ' . $e->getMessage();
}
?>В данном примере попытка деления на ноль в PHP 8 выбрасывает исключение DivisionByZeroError, которое перехватывается блоком catch. Если бы этот тип не был указан, исключение поднялось бы выше.
Типичные ошибки: использование слишком общего класса Exception вместо конкретного, что может скрыть неожиданные ошибки. Также забывают, что в PHP 7+ многие ошибки стали исключениями (TypeError, ArgumentCountError), их необходимо перехватывать.
Как обработать разные типы исключений отдельно?
Множественные блоки catch позволяют реагировать по-разному на разные классы исключений. Порядок имеет значение: первым ставится наиболее специфичный класс.
<?php
try {
processData($input);
} catch (InvalidArgumentException $e) {
echo 'Неверный аргумент: ' . $e->getMessage();
} catch (RuntimeException $e) {
echo 'Ошибка выполнения: ' . $e->getMessage();
} catch (Throwable $e) {
echo 'Неизвестная ошибка: ' . $e->getMessage();
}
?>В этом коде сначала проверяется InvalidArgumentException, затем RuntimeException, и только потом все остальное через Throwable. Throwable перехватывает любые ошибки и исключения в PHP 7+.
Проблемы: если поставить Throwable первым, все остальные catch никогда не сработают. Также необходимо помнить, что перехват Throwable может скрыть фатальные ошибки, которые следовало бы обработать выше.
Как выполнить код в любом случае после try или catch?
Блок finally выполняется всегда, независимо от того, было ли исключение и перехвачено ли оно. Он подходит для освобождения ресурсов.
<?php
try {
$file = fopen('file.txt', 'r');
} catch (Exception $e) {
echo 'Ошибка: ' . $e->getMessage();
} finally {
if (isset($file)) {
fclose($file);
}
}
?>В приведенном коде файл будет закрыт в любом случае, даже если произошла ошибка. Если исключение не перехвачено, finally все равно выполнится, после чего исключение пробросится дальше.
Типичные ошибки: если внутри finally выбросить новое исключение, оно заменит предыдущее (в PHP 7+ оно будет добавлено в цепочку, но станет основным). Следует избегать выбрасывания исключений в finally без необходимости.
Как создать собственное исключение для предметной области?
Расширение класса Exception позволяет создавать собственную иерархию исключений для более точного перехвата и дополнительной информации.
<?php
class DatabaseException extends Exception {}
class UserNotFoundException extends DatabaseException {}
try {
$user = findUser(42);
if (!$user) {
throw new UserNotFoundException('Пользователь с ID 42 не найден');
}
} catch (UserNotFoundException $e) {
echo 'Ошибка: ' . $e->getMessage();
}
?>В этом примере UserNotFoundException наследует от DatabaseException. Блок catch может перехватить сначала более узкое исключение, затем более общее.
Проблемы: чрезмерное количество пользовательских исключений усложняет понимание кода. Рекомендуется создавать не более 3-5 исключений на модуль. Также не стоит наследовать от базового Exception без необходимости.
Как обрабатывать стандартные ошибки PHP как исключения?
С помощью функции set_error_handler можно преобразовывать ошибки уровня E_WARNING, E_NOTICE и другие в исключения ErrorException, чтобы перехватывать их через try catch.
<?php
set_error_handler(function($severity, $message, $file, $line) {
throw new ErrorException($message, 0, $severity, $file, $line);
});
try {
$file = fopen('nonexistent.txt', 'r');
} catch (ErrorException $e) {
echo 'Перехвачено предупреждение: ' . $e->getMessage();
}
restore_error_handler();
?>После установки обработчика вызов fopen с несуществующим файлом вызовет Warning, который будет преобразован в ErrorException и перехвачен catch. Важно восстановить предыдущий обработчик через restore_error_handler().
Типичные ошибки: забывают восстановить старый обработчик, что может нарушить работу других частей приложения. Также в PHP 8 многие ошибки уже стали исключениями, поэтому данный метод применяется в основном для обратной совместимости.
Расширенные примеры использования try catch
Пример 1: вложенные блоки try catch
<?php
try {
try {
throw new Exception('Внутреннее исключение');
} catch (Exception $e) {
echo 'Внутренний catch: ' . $e->getMessage() . "\n";
throw $e;
} finally {
echo 'Внутренний finally' . "\n";
}
} catch (Exception $e) {
echo 'Внешний catch: ' . $e->getMessage() . "\n";
}
?>Внутренний catch: Внутреннее исключение Внутренний finally Внешний catch: Внутреннее исключение
Показывает порядок выполнения: внутренний catch, затем finally, затем внешний catch при повторном выбросе исключения.
Пример 2: цепочка исключений с предыдущим
<?php
class SpecificException extends Exception {}
try {
try {
throw new Exception('Исходная ошибка');
} catch (Exception $e) {
throw new SpecificException('Ошибка обработки', 0, $e);
}
} catch (SpecificException $e) {
echo $e->getMessage() . "\n";
$prev = $e->getPrevious();
if ($prev) {
echo 'Предыдущее: ' . $prev->getMessage();
}
}
?>Ошибка обработки Предыдущее: Исходная ошибка
Третий аргумент конструктора Exception позволяет передать предыдущее исключение, создавая цепочку.
Пример 3: try с finally без catch (проброс исключения)
<?php
function process() {
try {
throw new Exception('Ошибка');
} finally {
echo 'Очистка выполнена' . "\n";
}
}
try {
process();
} catch (Exception $e) {
echo 'Перехвачено: ' . $e->getMessage();
}
?>Очистка выполнена Перехвачено: Ошибка
Исключение не перехватывается внутри process(), но finally выполняется, после чего исключение всплывает во внешний try.
Пример 4: пользовательское исключение с дополнительными данными
<?php
class ValidationException extends Exception {
private array $errors;
public function __construct(string $message, array $errors = []) {
parent::__construct($message);
$this->errors = $errors;
}
public function getErrors(): array {
return $this->errors;
}
}
try {
$data = ['email' => 'invalid'];
$errors = [];
if (!filter_var($data['email'], FILTER_VALIDATE_EMAIL)) {
$errors['email'] = 'Неверный формат email';
}
if (!empty($errors)) {
throw new ValidationException('Ошибки валидации', $errors);
}
} catch (ValidationException $e) {
echo $e->getMessage() . "\n";
print_r($e->getErrors());
}
?>Ошибки валидации
Array
(
[email] => Неверный формат email
)Класс ValidationException содержит дополнительное свойство errors, доступное через getErrors().
Пример 5: преобразование ошибок в исключения через ErrorException (для Warnings)
<?php
set_error_handler(function($errno, $errstr) {
throw new ErrorException($errstr, $errno);
});
try {
$file = fopen('nonexistent.txt', 'r');
} catch (ErrorException $e) {
echo 'Перехвачено предупреждение: ' . $e->getMessage();
}
restore_error_handler();
?>Перехвачено предупреждение: fopen(nonexistent.txt): failed to open stream: No such file or directory
Демонстрирует использование set_error_handler для перехвата предупреждений как исключений.