Исключения в PHP 8: try/catch, finally и грамотная обработка ошибок

Исключения в PHP 8: try/catch, finally и грамотная обработка ошибок

Исключения в PHP 8: try/catch, finally и грамотная обработка ошибок

Запрос, который чаще всего набирают начинающие: «исключения в PHP как использовать». Это руководство закрывает вопрос: разберём try, catch, finally, иерархию Throwable, глобальные обработчики ошибок и лучшие практики для продакшена в PHP 8.

Что такое исключения в PHP 8

Исключение — это объект, сигнализирующий об ошибочной ситуации. В PHP вся иерархия ошибок/исключений восходит к интерфейсу Throwable:

  • Exception — «прикладные» ошибки (ошибки домена, валидации, бизнес-логики).
  • Error — фатальные ошибки движка (TypeError, ValueError, ParseError и др.).
  • Ловить можно и конкретные типы, и общий Throwable — в зависимости от задач.

    Базовый шаблон try/catch/finally

    <?php
    function readConfig(string $path): string {
        if (!is_readable($path)) {
            throw new RuntimeException("Файл конфигурации недоступен: $path");
        }
        $h = fopen($path, 'r');
        if ($h === false) {
            throw new RuntimeException("Не удалось открыть файл: $path");
        }
        try {
            return stream_get_contents($h) ?: '';
        } finally {
            // finally выполнится всегда — даже при throw в try
            fclose($h);
        }
    }
    
    try {
        $content = readConfig(__DIR__ . '/config.json');
        echo "OKn";
    } catch (RuntimeException $e) {
        error_log($e->getMessage());
        http_response_code(500);
        echo "Произошла ошибка. Попробуйте позже.";
    }
    

    Здесь finally гарантирует освобождение ресурса. Это критично для файлов, сокетов и соединений.

    Множественные блоки catch и объединённые типы

    В PHP 8 можно ловить разные типы в отдельных блоках или объединять их в один catch через «|».

    <?php
    function parsePositiveInt(string $value): int {
        if (!ctype_digit($value)) {
            throw new InvalidArgumentException("Ожидали целое число, получили: $value");
        }
        $n = (int)$value;
        if ($n <= 0) {
            throw new DomainException("Число должно быть > 0, получено: $n");
        }
        return $n;
    }
    
    try {
        $n = parsePositiveInt($_GET['limit'] ?? '0');
        echo "Будем обрабатывать $n элементов";
    } catch (InvalidArgumentException|DomainException $e) {
        http_response_code(400);
        echo "Неверный параметр: " . htmlspecialchars($e->getMessage(), ENT_QUOTES, 'UTF-8');
    } catch (Throwable $e) {
        // Запасной вариант: логируем всё прочее
        error_log($e);
        http_response_code(500);
        echo "Внутренняя ошибка сервера";
    }
    

    Exception vs Error: что ловить в продакшене

    Ошибка уровня Error (например, TypeError, ValueError) сигнализирует о проблемах на уровне кода/типов. Их полезно логировать и отображать понятный ответ пользователю, не «падая» белым экраном. В front controller (index.php) обычно ловят общий Throwable.

    <?php
    // index.php — пример глобального «щитка»
    
    set_exception_handler(function (Throwable $e) {
        error_log($e);
        http_response_code(500);
        echo "Что-то пошло не так. Мы уже чиним.";
    });
    
    // Дальше — ваш роутер/приложение
    require __DIR__ . '/app.php';
    

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

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

    <?php
    class PaymentException extends RuntimeException {}
    class PaymentDeclined extends PaymentException {}
    class PaymentGatewayDown extends PaymentException {}
    
    function charge(int $amount): void {
        // Демонстрация: разный тип ошибок для разных сценариев
        if ($amount > 10_000) {
            throw new PaymentDeclined("Лимит превышен");
        }
        if (!rand(0, 3)) {
            throw new PaymentGatewayDown("Шлюз недоступен");
        }
    }
    
    try {
        charge(12000);
    } catch (PaymentDeclined $e) {
        // Можно предложить другой способ оплаты
        echo "Платёж отклонён: {$e->getMessage()}";
    } catch (PaymentGatewayDown $e) {
        // Сообщить о технических работах
        echo "Проблема со шлюзом. Попробуйте позже.";
    }
    

    Перевод предупреждений PHP в исключения (ErrorException)

    Чтобы не пропускать предупреждения/notice, можно конвертировать их в исключения через set_error_handler:

    <?php
    set_error_handler(function (int $severity, string $message, string $file, int $line): bool {
        // E_DEPRECATED и др. — на ваше усмотрение
        if (!(error_reporting() & $severity)) {
            return false; // игнорируем отключённые уровни
        }
        throw new ErrorException($message, 0, $severity, $file, $line);
    });
    
    try {
        // Вызовет E_WARNING -> будет брошен ErrorException
        file_get_contents('/path/does/not/exist');
    } catch (ErrorException $e) {
        error_log($e);
    }
    

    JSON и исключения: JSON_THROW_ON_ERROR

    В PHP 8 удобно парсить JSON, не проверяя json_last_error вручную — используйте флаг JSON_THROW_ON_ERROR:

    <?php
    $json = '{"limit": 10}';
    
    try {
        $data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
        echo $data['limit'];
    } catch (JsonException $e) {
        error_log("Некорректный JSON: {$e->getMessage()}");
    }
    

    Глобальные обработчики и завершение скрипта

    Для перехвата фатальных ошибок на завершении скрипта используйте register_shutdown_function. Это не полноценная замена исключениям, но поможет зафиксировать критический сбой:

    <?php
    register_shutdown_function(function () {
        $error = error_get_last();
        if ($error) {
            error_log('[FATAL] ' . json_encode($error, JSON_UNESCAPED_UNICODE));
        }
    });
    

    Настройки окружения для продакшена

  • error_reporting(E_ALL) — логируйте всё.
  • display_errors = Off — не показывайте стек-трейсы пользователю.
  • log_errors = On, укажите error_log — централизованный лог ошибок.
  • Разделяйте конфиги для dev/prod, используйте переменные окружения.
  • Лучшие практики обработки исключений

  • Бросайте исключения по факту исключительных ситуаций, не для обычного потока управления.
  • Ловите как можно ближе к месту, где вы можете корректно восстановиться или вернуть понятный ответ.
  • Используйте собственные исключения для домена, а встроенные — для общих ошибок (InvalidArgumentException, RuntimeException и т. п.).
  • Всегда логируйте неожиданные Throwable на уровне входной точки приложения.
  • В finally освобождайте ресурсы: файлы, локи, временные данные.
  • Добавляйте в исключения контекст: идентификаторы запросов, пользователя, важные параметры (без секретов).
  • При пробросе исключения сохраняйте «предыдущее»: throw new RuntimeException(‘msg’, 0, $e).
  • Короткая шпаргалка ошибок и исключений

  • Throwable — общий предок, можно ловить всё сразу.
  • Exception — прикладные ошибки; Error — ошибки уровня движка (TypeError, ValueError).
  • JSON_THROW_ON_ERROR — удобный и безопасный разбор JSON.
  • set_error_handler + ErrorException — перевод предупреждений в исключения.
  • set_exception_handler — централизованная обработка и логирование.
  • Хочется системно прокачать PHP?

    Разобраться глубже с PHP и базами данных поможет практический курс с проектами и обратной связью. Рекомендую посмотреть программу и стартовать: Пройти интенсив «PHP и MySQL с Нуля до Гуру 3.0″ →

    Теперь вы знаете, как устроены исключения в PHP 8 и как строить надёжную обработку ошибок без «белых экранов». Возьмите примеры из статьи за основу и адаптируйте под архитектуру вашего проекта.

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий