- Выбрасывать и перехватывать исключения с помощью
throw,try,catchиfinally - Объяснять иерархию
Throwable,ExceptionиErrorи писать собственное исключение - Правильно настраивать вывод ошибок для разработки и продакшена
Платёжная система не отвечает, пользователь прислал сломанный JSON, база данных недоступна — на реальном сайте такое случается каждый день. Хорошая программа не показывает «белый экран»: она перехватывает проблему, выдаёт пользователю понятное сообщение, а подробности записывает в журнал. Главный инструмент для этого в PHP — исключения (exceptions).
try, catch и finally
Обнаружив проблему, код выбрасывает исключение с помощью throw: функция сразу останавливается, а исключение поднимается по цепочке вызовов, пока не найдёт блок catch, который его перехватит. Опасный код помещают в блок try, catch перехватывает исключение определённого типа, а finally выполняется в любом случае — было исключение или нет.
<?php
function divide(int $a, int $b): float
{
if ($b === 0) {
throw new InvalidArgumentException("Cannot divide by zero");
}
return $a / $b;
}
foreach ([4, 0] as $b) {
try {
$result = divide(12, $b);
echo "12 / $b = $result\n";
} catch (InvalidArgumentException $e) {
echo "Error: ", $e->getMessage(), "\n";
} finally {
echo "-- done with $b\n";
}
}12 / 4 = 3 -- done with 4 Error: Cannot divide by zero -- done with 0
При $b = 0 строка с echo так и не выполнилась: после throw управление сразу перешло в catch. А finally сработал на обоих проходах цикла. Полезные методы объекта исключения: getMessage() — сообщение, getCode() — код, getFile() и getLine() — место ошибки, getPrevious() — предыдущее исключение.
Exception и Error
Начиная с PHP 7 общий родитель всего, что можно выбросить, — интерфейс Throwable. От него отходят две ветви: **Exception — проблемы в логике самой программы (неверный аргумент, файл не найден, не удалось подключиться к базе) и Error** — внутренние ошибки PHP (неверный тип, деление на ноль, вызов несуществующего метода).
| Ветвь | Часто встречающиеся классы | Когда |
|---|---|---|
Exception | InvalidArgumentException, RuntimeException, JsonException, PDOException | ожидаемые проблемы: их можно перехватить и обработать |
Error | TypeError, ValueError, DivisionByZeroError, ArgumentCountError, UnhandledMatchError | обычно ошибка в коде: её нужно исправить |
<?php
$tests = [
fn() => intdiv(10, 0),
fn() => json_decode("{bad json", flags: JSON_THROW_ON_ERROR),
fn() => throw new RuntimeException("Disk is full"),
];
foreach ($tests as $test) {
try {
$test();
} catch (Throwable $e) {
echo get_class($e), ": ", $e->getMessage(), "\n";
}
}DivisionByZeroError: Division by zero JsonException: Syntax error RuntimeException: Disk is full
Собственные исключения
Чтобы создать собственный класс исключения, унаследуйся от существующего — обычно от RuntimeException или DomainException. Пусть имя ясно описывает проблему, а дополнительные данные при необходимости хранятся в свойстве. Тогда блок catch сможет точно отреагировать именно на эту ситуацию.
<?php
class InsufficientFundsException extends RuntimeException
{
public function __construct(public readonly float $missing)
{
parent::__construct("Not enough money: missing $missing AZN");
}
}
class Wallet
{
public function __construct(private float $balance) {}
public function pay(float $amount): void
{
if ($amount > $this->balance) {
throw new InsufficientFundsException($amount - $this->balance);
}
$this->balance -= $amount;
echo "Paid $amount, left {$this->balance}\n";
}
}
$wallet = new Wallet(30);
try {
$wallet->pay(12);
$wallet->pay(25.5);
$wallet->pay(1);
} catch (InsufficientFundsException $e) {
echo $e->getMessage(), "\n";
echo "Top up at least ", ceil($e->missing), " AZN\n";
}Paid 12, left 18 Not enough money: missing 7.5 AZN Top up at least 8 AZN
$data = json_decode($input);
if ($data === null) {
// broken JSON or the value null?
echo json_last_error_msg();
}try {
$data = json_decode($input, flags: JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
echo "Invalid JSON: ", $e->getMessage();
}null может означать и ошибку, и корректное JSON-значение (null). Исключение же сообщает об ошибке однозначно.Предупреждения и вывод ошибок
Некоторые проблемы не выбрасывают исключение, а лишь выдают предупреждение: например, несуществующая переменная даёт Warning: Undefined variable $x, а устаревшая функция — сообщение Deprecated. Какие сообщения будут видны, определяют настройки php.ini. Золотое правило: во время разработки видеть всё, а в продакшене (production) ничего не показывать пользователю и всё записывать в журнал.
Настройка (php.ini) | Разработка | Продакшен |
|---|---|---|
error_reporting | E_ALL | E_ALL |
display_errors | On | Off |
log_errors | On | On |
error_reporting(E_ALL); ini_set('display_errors', '1');Главное
throwвыбрасывает исключение;tryсодержит опасный код,catchперехватывает конкретный тип, аfinallyвыполняется всегда.- У
Throwableдве ветви:Exception(ожидаемые проблемы) иError(внутренние ошибки PHP). - Наследуй собственное исключение от существующего класса и давай ему понятное имя; дополнительные данные храни в свойстве.
- В PHP 8
throw— выражение:$x = $arr[$k] ?? throw new ...; - При разработке —
display_errors = On, а в продакшене —Offвместе сlog_errors = On.
Проверь себя
Вопросов: 10. Каждый правильный ответ приносит XP.
finally?