PHP try catch примеры обработки исключений с finally
Try catch PHP перехватывает исключения и запускает обработчик ошибки. Поместите рискованную операцию в try, а реакцию на сбой – в catch. Блок finally поможет освободить открытые ресурсы. Примеры показывают обработку разных типов, собственные исключения и запись подробностей в лог.
Пример Try Catch PHP
Начните с разбора JSON, который приложение получает от внешнего сервиса. Флаг JSON_THROW_ON_ERROR заставляет json_decode() выбросить JsonException при некорректном JSON.
Сохраните пример в файл example.php и выполните его установленным интерпретатором PHP командой php example.php.
<?php
$json = '{"city": }';
try {
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
echo 'JSON прочитан: ' . $data['city'];
} catch (JsonException $e) {
echo 'Ошибка JSON: ' . $e->getMessage();
}
echo PHP_EOL . 'Обработка завершена.';
Некорректная строка вызывает исключение, поэтому присваивание $data не завершается. PHP переходит в подходящий блок catch и выводит сообщение. После обработчика скрипт печатает «Обработка завершена».
Замените значение на '{"city": "Москва"}' и запустите файл повторно. Теперь программа выведет «JSON прочитан: Москва», пропустив обработчик ошибки.
Что делает каждый блок
tryсодержит операцию, которая может выбросить исключение. После выброса PHP пропускает оставшийся код этого блока.catchперехватывает объект подходящего типа. Обработчикcatch (JsonException $e)получает исключение в переменной$e.finallyвыполняет завершающие действия при обычном выходе из конструкции, включая выход после исключения. В нем удобно закрывать файлы и освобождать другие ресурсы.
Обработчик не возвращает выполнение на строку, вызвавшую исключение. Если ошибка обработана без повторного выброса, выполнение продолжается после конструкции.
Синтаксис и особенности блоков описывает документация PHP об исключениях. Все примеры статьи подходят для PHP 8.x.
Catch без переменной и throw как выражение
Начиная с PHP 8.0, catch допускает отсутствие переменной исключения. Используйте такую запись, если подробности ошибки обработчику не нужны.
<?php
try {
json_decode('{', true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException) {
echo 'Ответ сервиса содержит некорректный JSON.';
}
С PHP 8.0 оператор throw также работает внутри выражений. Например, оператор ?? позволяет выбросить исключение при отсутствии значения.
<?php
$input = [];
try {
$name = $input['name']
?? throw new InvalidArgumentException('Не указано имя.');
echo $name;
} catch (InvalidArgumentException $e) {
echo $e->getMessage();
}
Какие ошибки перехватывают Exception и Throwable
В PHP 7+ интерфейс Throwable объединяет две основные ветви. Класс Exception представляет исключения, а класс Error – ошибки выполнения, представленные объектами.
ExceptionвключаетRuntimeException,InvalidArgumentException,JsonExceptionи другие классы исключений.ErrorвключаетTypeError,DivisionByZeroErrorи другие классы ошибок.Throwableзадает общий тип для обеих ветвей.
Блок catch (Exception $e) не перехватывает TypeError и DivisionByZeroError. Для перехвата обеих ветвей используйте catch (Throwable $e).
<?php
try {
echo intdiv(10, 0);
} catch (Throwable $e) {
echo 'Перехвачен тип: ' . get_class($e);
}
Программа выведет «Перехвачен тип: DivisionByZeroError» и завершится без непойманного исключения. Если заменить тип обработчика на Exception, ошибка останется неперехваченной.
Справка о Throwable описывает общий интерфейс и его методы. Широкий перехват удобен на границе приложения, где формируется ответ пользователю.
Обычные предупреждения PHP не превращаются в исключения автоматически. Например, неудачный fopen() может выдать Warning и вернуть false. Один только catch (Throwable $e) такое предупреждение не перехватит.
Как использовать несколько блоков catch
Разделяйте обработчики, если разные сбои требуют разной реакции приложения. PHP выбирает первый подходящий catch, остальные обработчики этой конструкции пропускает.
<?php
function decodeResponse(string $json): array
{
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
if (!is_array($data)) {
throw new InvalidArgumentException('Ожидался объект или массив JSON.');
}
return $data;
}
try {
$data = decodeResponse('{');
echo 'Ответ принят.';
} catch (JsonException $e) {
echo 'Ответ сервиса содержит ошибку JSON.';
} catch (InvalidArgumentException $e) {
echo 'Структура ответа не подходит приложению.';
} catch (Exception $e) {
echo 'Операция завершилась ошибкой.';
}
Ставьте частные типы перед общими, чтобы сохранить разные реакции. Если первым поставить Exception, он перехватит также JsonException и InvalidArgumentException.
Запустите пример с аргументами '{', 'null' и '{"ok": true}'. Первый вызовет ошибку JSON, второй – ошибку структуры. Третий завершится сообщением «Ответ принят».
Как объединить типы в одном catch
С PHP 7.1 синтаксис catch (A | B $e) объединяет несколько типов. Используйте его, когда обработка этих ошибок полностью совпадает.
В примере с функцией decodeResponse() замените цепочку обработчиков таким блоком:
catch (JsonException | InvalidArgumentException $e) {
echo 'Сервис вернул неподходящий ответ.';
}Оба типа попадут в один обработчик с общим сообщением. Объединенный catch не меняет класс самого объекта исключения.
Как закрыть ресурс в finally
Блок finally освобождает ресурс независимо от результата основной операции. Пример открывает временный поток, записывает JSON и читает его обратно.
<?php
$handle = null;
$json = '{"city": "Москва"}';
try {
$handle = fopen('php://temp', 'w+');
if ($handle === false) {
throw new RuntimeException('Не удалось открыть временный поток.');
}
if (fwrite($handle, $json) === false) {
throw new RuntimeException('Не удалось записать JSON.');
}
rewind($handle);
$contents = stream_get_contents($handle);
if ($contents === false) {
throw new RuntimeException('Не удалось прочитать JSON.');
}
$data = json_decode($contents, true, 512, JSON_THROW_ON_ERROR);
echo 'JSON прочитан.' . PHP_EOL;
} catch (JsonException | RuntimeException $e) {
echo 'Обработка не выполнена.' . PHP_EOL;
} finally {
if (is_resource($handle)) {
fclose($handle);
echo 'Ресурс закрыт.' . PHP_EOL;
}
}
При корректном JSON скрипт напечатает «JSON прочитан» и «Ресурс закрыт». Замените $json на '{' и повторите запуск. Теперь появится «Обработка не выполнена», но ресурс все равно закроется.
Проверка is_resource() защищает вызов fclose() при неудачном открытии потока. Сам fopen() при сбое также может выдать предупреждение.
Блок finally выполняется и при return внутри try. Однако exit() прекращает скрипт без выполнения этого блока. Не используйте exit() внутри конструкции, если рассчитываете на освобождение ресурса.
Не возвращайте результат из finally: такой return может скрыть исключение. Оставьте этому блоку только завершающие действия.
Когда используются исключения?
Исключения используются, когда результат операции отличается от того, что ожидало ваше приложение. Типичные ситуации включают недоступную базу, ошибку запроса и некорректный ответ API.
Для чтения существующего CSV можно использовать SplFileObject. При невозможности открыть файл его конструктор выбрасывает RuntimeException.
<?php
try {
$file = new SplFileObject(__DIR__ . '/my_file.csv', 'r');
$row = $file->fgetcsv();
echo 'CSV открыт.';
} catch (RuntimeException $e) {
echo 'CSV-файл недоступен.';
}
Создайте рядом файл my_file.csv со строкой name,price. При запуске появится сообщение «CSV открыт». Переименуйте CSV и повторите запуск, чтобы получить «CSV-файл недоступен».
Как перехватить ошибку PDO
Режим PDO::ERRMODE_EXCEPTION заставляет PDO выбрасывать исключения при ошибках запросов. Ошибка подключения выбрасывает PDOException независимо от режима обработки ошибок.
Пример использует SQLite, чтобы не вводить учетные данные базы. Для запуска потребуется расширение pdo_sqlite.
<?php
try {
$pdo = new PDO('sqlite::memory:', null, null, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$pdo->exec('CREATE TABLE items (id INTEGER PRIMARY KEY)');
$pdo->query('SELECT * FROM missing_table');
} catch (PDOException $e) {
echo 'Запрос к базе данных не выполнен.';
}
Таблицы missing_table нет, поэтому запрос попадет в catch. Замените ее имя на items, и запрос выполнится без сообщения об ошибке.
Для MySQL изменится строка подключения, но тип обработчика останется прежним.
Не выводите посетителю сообщение PDO: оно может раскрыть структуру базы.
Как создать и выбросить собственное исключение
Собственный класс позволяет отличить ошибку предметной области от технического сбоя. Наследуйте его от Exception или подходящего стандартного подкласса.
<?php
class MyException extends Exception
{
}
function reserveItems(int $requested, int $available): void
{
if ($requested > $available) {
throw new MyException('Недостаточно товара на складе.');
}
}
try {
reserveItems(3, 2);
echo 'Товар зарезервирован.';
} catch (MyException $e) {
echo $e->getMessage();
}
Вызов с числами 3 и 2 выведет сообщение о нехватке товара. Замените запрошенное количество на 1, чтобы увидеть успешное резервирование.
Конструкция throw new Exception('Сообщение') создает и сразу выбрасывает стандартное исключение. Собственный класс использует тот же синтаксис throw new, но задает отдельный тип для перехвата.
Нужно ли перехватывать все исключения?
Перехватывайте исключение там, где код способен осмысленно обработать сбой. Обработчик может предложить повторную попытку, вернуть запасной результат или отменить изменения.
Если погодный API недоступен, покажите «Информация о погоде недоступна». Остальная страница при этом может продолжить работу. Если приложение полностью зависит от базы, продолжать запрос после потери подключения обычно бессмысленно.
Не оставляйте пустые обработчики: они скрывают ошибки и затрудняют диагностику. Для непредвиденного сбоя сохраните подробности и передайте ошибку выше.
Повторный выброс и цепочка исключений
Оператор throw $e повторно выбрасывает тот же объект исключения. Используйте его после локальных действий, если окончательное решение принимает вызывающий код.
<?php
function decodeWithLog(string $json): mixed
{
try {
return json_decode($json, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
error_log($e->getMessage());
throw $e;
}
}
try {
decodeWithLog('{');
} catch (JsonException $e) {
echo 'Не удалось прочитать ответ сервиса.';
}
Здесь локальный обработчик записывает сообщение в журнал PHP. Внешний обработчик получает то же исключение и формирует ответ.
Для добавления контекста передайте исходное исключение третьим аргументом конструктора. Метод getPrevious() затем вернет исходную причину.
<?php
function parseConfig(string $json): mixed
{
try {
return json_decode($json, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
throw new RuntimeException('Ошибка чтения конфигурации.', 0, $e);
}
}
try {
parseConfig('{');
} catch (RuntimeException $e) {
echo $e->getMessage();
$previous = $e->getPrevious();
if ($previous !== null) {
error_log($previous->getMessage());
}
}
Обертка сохраняет понятный контекст, а исходное исключение объясняет техническую причину. Не дублируйте логирование на каждом уровне без отдельной задачи.
Глобальный обработчик и запись подробностей в лог
Функция set_exception_handler() назначает обработчик непойманных исключений. Зарегистрируйте его до выполнения основного кода приложения.
Этот самостоятельный пример предназначен для запуска командой php example.php. Он записывает журнал в файл рядом с примером. Каталог должен разрешать запись пользователю, который запускает PHP.
<?php
set_exception_handler(function (Throwable $e): void {
$details = sprintf(
"[%s] %s; code=%s; file=%s; line=%d\n%s\n",
get_class($e),
$e->getMessage(),
(string) $e->getCode(),
$e->getFile(),
$e->getLine(),
$e->getTraceAsString()
);
error_log($details, 3, __DIR__ . '/app-error.log');
if (PHP_SAPI !== 'cli') {
http_response_code(500);
}
echo 'Операция недоступна. Попробуйте позднее.';
});
throw new RuntimeException('Тестовая ошибка приложения.', 1001);
echo 'Эта строка не выполнится.';
На экране появится только заглушка «Операция недоступна. Попробуйте позднее». Файл app-error.log получит сообщение, код 1001, имя файла, строку и стек вызовов.
getMessage()возвращает текст ошибки.getCode()возвращает код, заданный при создании исключения.getFile()иgetLine()указывают место возникновения исключения.getTraceAsString()возвращает стек вызовов текстовой строкой.
Глобальный обработчик не возобновляет выполнение после проблемной строки. После его завершения скрипт останавливается, поэтому последнее echo не сработает.
На рабочем сайте храните журнал вне публичного каталога. Ограничьте доступ к нему и отключите показ технических ошибок посетителям. Сообщения и стек могут содержать пути, параметры и чувствительные данные.
Как выбрать место обработки исключения
Выбирайте обработчик по тому, какую реакцию способен выполнить ваш код.
- Если операция допускает запасной результат, обработайте конкретный тип рядом с вызовом.
- Если открыли ресурс, освободите его в
finally. - Если нужно добавить контекст, создайте исключение с исходной причиной.
- Если локальный код не может восстановить работу, передайте исключение выше.
- Если исключение осталось непойманным, глобальный обработчик запишет подробности и покажет заглушку.
Проверяйте каждый обработчик двумя запусками: с верными данными и с намеренной ошибкой. В обоих случаях убедитесь, что программа выдает ожидаемый результат и закрывает открытые ресурсы.
Популярное за неделю
- Как создать сайт самому в 2026: 4 способа + пошаговая инструкция 34 мин чтения
- Лучшие конструкторы для создания сайтов — плюсы, минусы и сравнения 22 мин чтения
- Как сделать редирект на PHP 6 мин чтения
- Основы ООП в PHP: принципы и примеры 6 мин чтения
- Адаптивный помесячный календарь на AJAX и PHP 2 мин чтения
- Как создать формы регистрации и авторизации на PHP 5 мин чтения
Комментарии
Все исключния нужно обрабытывать. Если ошибка базы данных, то пользователю не будет приятно увидеть ее на своем экране. К тому же использовать исключения просто удобно, их можно отлавливать даже в одном месте, логировать и выводить дружелюбное сообщение об ошибке, либо перенаправлять на 404 страницу. Это удобный инструмент которым, к сожалению не все умеют пользоватся.
Здравствуйте! Не до конца понял, зачем использовать try и catch. Ведь можно логикой if else оценить результаты ошибочной операции и вывести пользователю понятные сообщения о том, что именно пошло не так
В том-то и дело, зачем городить конструкцию try-catch, если if-else можно буквально одной строкой сделать то же самое?
Очевидно, что try-catch должен использоваться в каких-то более сложных моментах.