Конструкция 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 для перехвата предупреждений как исключений.

PHP try catch - comments

En
Php try catch (php)