Исключения в PHP
Исключения в PHP позволяют обрабатывать неожиданные ошибки и проблемы времени выполнения. Узнайте, как использовать try, catch и finally.
Исключение — это объект, представляющий ошибку или неожиданное условие, прерывающее нормальный ход выполнения программы. Вместо того чтобы возвращать код ошибки, который вызывающий код может забыть проверить, код «бросает» исключение; среда выполнения разворачивает стек вызовов до тех пор, пока не найдёт подходящий блок catch. Если ни один блок не перехватит исключение, скрипт завершится с фатальной ошибкой.
В этой главе рассматривается, как бросать и перехватывать исключения, методы, доступные у каждого объекта исключения (getMessage(), getCode(), getLine(), getFile()), блок finally, несколько блоков catch, пользовательские классы исключений, а также разница между Exception и Error.
Что такое исключение в PHP?
Исключение в PHP — это объект, унаследованный от встроенного класса Exception (или, в более широком смысле, реализующий интерфейс Throwable). Когда что-то идёт не так — отсутствует файл, передан недопустимый аргумент, не удалось подключиться к базе данных — вы создаёте один из таких объектов и бросаете его с помощью throw. Бросок немедленно прерывает текущий путь выполнения и передаёт управление ближайшему обработчику.
Используйте исключения тогда, когда функция не может продолжить работу, а вызывающий код — подходящее место для принятия решения о дальнейших действиях. Не используйте исключения для обычного управления потоком (например, ситуация «пользователь не найден» лучше выражается через возвращаемое значение).
Бросок исключения
Ключевое слово throw вызывает исключение, за которым следует новый экземпляр класса исключения. Конструктор принимает необязательное сообщение, целочисленный код и предыдущее исключение (для создания цепочки):
<?php
function divide(int $a, int $b): float
{
if ($b === 0) {
throw new InvalidArgumentException('Division by zero is not allowed.');
}
return $a / $b;
}
try {
echo divide(10, 0);
} catch (InvalidArgumentException $e) {
echo 'Error: ' . $e->getMessage();
}
?>Вывод:
Error: Division by zero is not allowed.Код внутри try выполняется в обычном режиме до момента срабатывания throw. После этого оставшаяся часть блока try пропускается, и выполняется соответствующий блок catch.
Обработка исключений с помощью try / catch
Код, который может завершиться ошибкой, оборачивается в блок try, а восстановление после ошибки производится в блоке catch. Переменная в catch (здесь $e) хранит объект исключения, предоставляющий несколько методов только для чтения:
| Метод | Возвращает |
|---|---|
getMessage() | Читаемое человеком сообщение |
getCode() | Целочисленный код, переданный в конструктор |
getLine() | Номер строки, где было брошено исключение |
getFile() | Файл, в котором оно было брошено |
getPrevious() | Связанное «предыдущее» исключение, если есть |
getTraceAsString() | Трассировка стека в виде строки |
<?php
try {
throw new Exception('Something failed', 42);
} catch (Exception $e) {
echo 'Message: ' . $e->getMessage() . PHP_EOL;
echo 'Code: ' . $e->getCode() . PHP_EOL;
}
?>Вывод:
Message: Something failed
Code: 42Перехват нескольких типов исключений
Один блок try может содержать несколько блоков catch. PHP проверяет их сверху вниз и выполняет первый, тип которого совпадает с типом брошенного исключения. Начиная с PHP 7.1 можно также перехватывать несколько несвязанных типов в одном блоке с помощью оператора «вертикальная черта» (|):
<?php
try {
throw new RuntimeException('Network is down');
} catch (InvalidArgumentException $e) {
echo 'Bad argument: ' . $e->getMessage();
} catch (RuntimeException | LogicException $e) {
echo 'Runtime/logic problem: ' . $e->getMessage();
}
?>Вывод:
Runtime/logic problem: Network is downПорядок важен: более конкретные типы исключений следует перечислять перед их родительскими классами, иначе широкий catch перехватит всё первым.
Блок finally
Блок finally необязателен, но полезен. Его код выполняется всегда — независимо от того, было ли брошено исключение, и даже если в блоке try или catch выполняется return. Это делает его подходящим местом для очистки ресурсов, например закрытия файлового дескриптора или освобождения блокировки:
<?php
try {
echo 'Open resource' . PHP_EOL;
throw new Exception('Boom');
} catch (Exception $e) {
echo 'Caught: ' . $e->getMessage() . PHP_EOL;
} finally {
echo 'Cleanup always runs' . PHP_EOL;
}
?>Вывод:
Open resource
Caught: Boom
Cleanup always runsПользовательские классы исключений
Помимо встроенных типов вы можете определить собственные классы исключений, расширив Exception. Пользовательский класс позволяет хранить дополнительные данные (например, неверное значение) и даёт вызывающему коду возможность перехватывать именно ваш тип ошибки, не затрагивая посторонние:
<?php
class InsufficientFundsException extends Exception
{
private float $shortfall;
public function __construct(float $shortfall)
{
$this->shortfall = $shortfall;
parent::__construct("Short by $shortfall");
}
public function getShortfall(): float
{
return $this->shortfall;
}
}
try {
throw new InsufficientFundsException(25.5);
} catch (InsufficientFundsException $e) {
echo $e->getMessage() . PHP_EOL;
echo 'Need ' . $e->getShortfall() . ' more.';
}
?>Вывод:
Short by 25.5
Need 25.5 more.PHP также поставляется с набором готовых SPL-исключений — InvalidArgumentException, RuntimeException, LengthException и другими, — поэтому зачастую нет необходимости создавать собственные.
Exception и Error
Начиная с PHP 7, внутренние сбои движка (например, ошибка типа или вызов неопределённого метода) выбрасываются как объекты Error, а не Exception. Оба типа реализуют интерфейс Throwable. Обычный catch (Exception $e) не перехватит Error. Чтобы обрабатывать оба случая, перехватывайте интерфейс:
<?php
try {
$result = 10 % 0; // throws a DivisionByZeroError
} catch (Throwable $e) {
echo get_class($e) . ': ' . $e->getMessage();
}
?>Вывод:
DivisionByZeroError: Modulo by zeroКак правило, используйте Exception для проблем, от которых приложение может восстановиться, а Error пусть остаётся для ошибок, которые следует исправить в коде, а не перехватывать.
Связанные темы
- Ошибки PHP — разница между ошибками и исключениями.
- Оператор
tryв PHP — подробный взгляд на конструкциюtry. set_exception_handler()— глобальный обработчик для неперехваченных исключений.- Классы и объекты PHP — основа для построения пользовательских классов исключений.
- Функции PHP — место, где исключения чаще всего бросаются.
Заключение
Исключения предоставляют PHP структурированный способ работы с ошибками: функция бросает исключение, когда не может продолжить выполнение, а вызывающий код перехватывает его для восстановления, логирования или повторного броска. Комбинируйте try, catch и finally, чтобы отделить основной путь выполнения от обработки ошибок и очистки ресурсов, используйте пользовательские классы для моделирования ошибок предметной области и помните, что ошибки на уровне движка приходят как Error (перехватываемые через Throwable). Применение этих паттернов на практике сделает ваш код значительно надёжнее.