Исключения и обработка ошибок в PHP

Исключения в PHP: try/catch/finally, бросок через throw, создание своих классов исключений, SPL-исключения.

Исключение (exception) — это объект, который сигнализирует об ошибке во время выполнения программы. Механизм try/catch позволяет перехватить исключение и обработать его, не прерывая работу всего приложения.

Базовый синтаксис try/catch

Код, который может вызвать ошибку, помещают в блок try. Если ошибка возникнет — управление перейдёт в catch:

<?php
function divide(float $a, float $b): float {
    if ($b === 0.0) {
        throw new \InvalidArgumentException("Деление на ноль недопустимо");
    }
    return $a / $b;
}

try {
    echo divide(10, 2) . "\n";  // OK
    echo divide(5, 0);           // бросает исключение
} catch (\InvalidArgumentException $e) {
    echo "Ошибка: " . $e->getMessage();
}

Вывод:

5
Ошибка: Деление на ноль недопустимо

Метод getMessage() возвращает текст ошибки. Также полезны getCode() и getFile()/getLine() для отладки.

Несколько catch и finally

Можно перехватывать разные типы исключений отдельными блоками. Блок finally выполняется всегда — независимо от того, было ли исключение:

<?php
function processInput(mixed $input): int {
    if (!is_numeric($input)) {
        throw new \TypeError("Ожидается число, получено: " . gettype($input));
    }
    if ($input < 0) {
        throw new \RangeException("Число должно быть неотрицательным");
    }
    return (int) $input;
}

try {
    echo processInput("abc");
} catch (\TypeError $e) {
    echo "Тип некорректен: " . $e->getMessage() . "\n";
} catch (\RangeException $e) {
    echo "Диапазон некорректен: " . $e->getMessage() . "\n";
} finally {
    echo "Обработка завершена (выполняется всегда)\n";
}

Вывод:

Тип некорректен: Ожидается число, получено: string
Обработка завершена (выполняется всегда)

Свои классы исключений

Создавайте собственные классы исключений для разных ситуаций — это упрощает обработку ошибок в большом приложении:

<?php
class UserNotFoundException extends \RuntimeException {}
class UserBannedException extends \RuntimeException {
    public function __construct(string $username, int $code = 0) {
        parent::__construct("Пользователь «$username» заблокирован", $code);
    }
}

function findUser(string $username): array {
    $users = ["anna" => ["active" => true], "banned" => ["active" => false]];

    if (!isset($users[$username])) {
        throw new UserNotFoundException("Пользователь «$username» не найден");
    }
    if (!$users[$username]["active"]) {
        throw new UserBannedException($username);
    }
    return $users[$username];
}

foreach (["anna", "bob", "banned"] as $name) {
    try {
        $user = findUser($name);
        echo "$name: доступ разрешён\n";
    } catch (UserNotFoundException $e) {
        echo $e->getMessage() . "\n";
    } catch (UserBannedException $e) {
        echo $e->getMessage() . "\n";
    }
}

Вывод:

anna: доступ разрешён
Пользователь «bob» не найден
Пользователь «banned» заблокирован

Встроенные SPL-исключения

КлассКогда использовать
\InvalidArgumentExceptionнекорректный аргумент функции
\RuntimeExceptionошибка во время выполнения
\LogicExceptionлогическая ошибка программы
\RangeExceptionзначение вне допустимого диапазона
\TypeErrorнесовпадение типов
\OverflowExceptionпереполнение структуры данных

Не используйте голый catch (\Exception $e) для всего подряд — так вы скроете настоящую причину ошибки. Перехватывайте конкретные типы исключений.

Коротко

  • throw new Exception("...") — бросает исключение.
  • try/catch — перехватывает исключение; finally — выполняется всегда.
  • Разные типы исключений перехватывают отдельными блоками catch.
  • Создавайте собственные классы исключений, расширяя \RuntimeException или \LogicException.
  • PHP предоставляет богатую иерархию SPL-исключений — используйте их вместо голого \Exception.
Проверьте себя
1. Что выполнится в блоке finally?
AТолько если исключение не было выброшено
BТолько если исключение было перехвачено в catch
CВсегда — независимо от того, было ли исключение
DТолько если в try нет оператора return
2. Что нужно унаследовать, чтобы создать своё исключение для ошибок времени выполнения?
A\Exception напрямую
B\LogicException
C\RuntimeException
D\Error
3. Чем плох перехват голого catch (\Exception $e) на все случаи?
AЭто допустимо и является хорошей практикой
BЭто скрывает настоящую причину ошибки и затрудняет отладку
CТакой синтаксис не работает в PHP 8
DЭто слишком медленно из-за полиморфизма
Поддержать проект