Как работает try catch php
Перейти к содержимому

Как работает try catch php

  • автор:

Правильная работа с исключениями в PHP

Таким образом, все исключения вашего кода можно будет отличить от исключений не вашего кода.

2. Исключения должны быть иерархичны. У вас должен быть базовый класс исключений, от которого наследуются все исключения, бросаемые в вашем коде. Например, у вас в коде есть модуль для работы с файлами fileModule, объявите исключение, которое будет бросаться только этим модулем

class fileModuleException extends baseException

Если вам нужна еще бОльшая различимость ошибок, например, среди всех ошибок, связанных с работой с файлами, вы хотите различать ситуацию, когда файл не найден, то нужно объявить еще одно исключение

class fileNotFoundException extends fileModuleException < >

Соблюдая иерархичность, вы сможете различать исключения от разных модулей в вашем приложении. Я не призываю наплодить кучу исключений, для каждого модуля. Исключения должны проектироваться не от кода, а от ситуаций, которые вы хотите по-особенному обработать.
И обратная ситуация, не скупитесь сделать разные исключения, если того требует обстоятельства

try <
//.
> catch ( fileModuleException $e ) <
switch ( $e -> getCode ( ) ) < //так делать не надо
case 1 : echo ‘file not found’ ;
case 2 : echo ‘file not readable’ ;
//.
>
>

Чтобы такие ситуации в принципе не были возможны, можно «заглушить» code в базовом классе

function __construct ( $message = » , $code = 0 ) <
parent :: __construct ( $message , 0 ) ;
>

3. Не обрабатывайте исключения, если в данном контексте не понятно, как его обработать. Например, если вы следуете паттерну MVC, то в методе модели может быть не понятно, как обработать ошибку — как ее вывести, потому как за логику отвечает control, а за вывод view. Если не понятно, что делать с исключением, то «пробросьте» его дальше.

try <
$db -> begin ( ) ;
//.
$db -> commit ( ) ;
> catch ( Exception $e ) <
$db -> rollback ( ) ;
throw $e ;
>

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

try <
//.
> catch ( Exception $e ) <
throw new baseException ( $message , 0 , $e ) ; //не разрывайте цепь
>

Тут очень важный момент — не разрывать цепь исключений. Третьим параметром передается изначальное исключение. Этот код нативно работает в 5.3 и с доработкой в 5.2. При таком подходе стек вызовов будет «цельным» от самого первого броска исключения.

4. У вас должен быть глобальный обработчик исключений. Это может быть или try. catch на самом верхнем уровне или ExceptionHandler. Все исключения, которые добрались до глобального обработчика, считаются критическими, так как не были правильно обработаны ранее. Их надо залогировать.

5. Исключение это объект, соответственно его можно расширять под свои потребности. Допустим у вас многоязычное приложение и текст ошибки в бросаемом исключении нужно выводить пользователю. Соответственно это сообщение нужно переводить. Это не сложно, если сообщение без переменных частей, например, «Ошибка при выполнении операции». Но что делать, если в сообщение входят переменные части, например, «У вас недостаточно денег на балансе (1000). Нужно 2000». Тогда можно отдельно передать шаблон текста ошибки и отдельно сами переменные. Пример кода Старый пример кода.

6. Преобразуйте все ошибки утверждений (assertion fail) и не фатальные ошибки в исключения (см. мою предыдущую статью)

7. Никогда не глушите исключения без какой либо обработки

try <
//.
> catch ( Exception $e ) <
//ничего делаем
>

потому, что в противном случае ошибку из-за таких действий будет очень сложно найти. Нужно хотя бы логировать:

try <
//.
> catch ( Exception $e ) <
exceptionHandlerClass :: exceptionLog ( $e ) ;
>

8. Документируйте исключения. Указывайте в докблоке, какие исключения выбрасывает метод (таг @throws, можно указывать больше одного). Это упростит всем жизнь.

Вот в принципе и все, что нужно знать про исключения. Еще один интересный факт напоследок — исключения можно ловить по интерфейсу:

interface iException < >
class customException extends baseException implements iException < >
try <
//.
> catch ( iException $e ) <
//.
>

UPD исправлены замечания в комментариях:1, 2 и 3 (спасибо всем, кто поучаствовал в обсуждении).
Отдельное спасибо, хабраюзеру ckopobapkuh за активное участие

Try Catch PHP или исключения в PHP

Представляем вашему вниманию руководство для новичков о том, как использовать блоки Try Catch PHP . В этой статье я покажу, как перехватывать исключения.

Давайте сразу взглянем на пример сгенерированного исключения ( и впоследствии перехваченного ):

 > //Перехватываем (catch) исключение, если что-то идет не так. catch (Exception $ex) < //Выводим сообщение об исключении. echo $ex->getMessage(); >

В приведенном выше примере я продемонстрировал использование TRY и CATCH , в котором исключение всегда сгенерировано ( только ради примера ):

  1. Внутри блока TRY мы проверяем, равняется ли цифра 1 цифре 2 . Так как она не равняется ( и никогда не будет равняться ), мы генерируем исключение с сообщением “ 1 не равняется 2! ”;
  2. Внутри блока CATCH мы перехватываем исключение и выводим соответствующее сообщение.
  • TRY : внутри блока PHP try мы задаем логику приложения. Этот блок содержит код, который может или не может сгенерировать исключение;
  • CATCH : блок CATCH будет перехватывать любые исключения, проявившиеся в предыдущем блоке TRY . Код внутри блока CATCH будет исполнен только в случае обнаружения исключения;
  • FINALLY : если вы используете PHP 5.5 и выше, то вы можете использовать блок FINALLY . Расположенный в нем код исполняется всегда, вне зависимости от того, было ли обнаружено исключение.

Когда используются исключения?

Исключения используются, когда результат операции отличается от того, что ожидало ваше приложение. К примеру, если ваше приложение пытается прочитать CSV-файл на сервере, а этого файла не существует, то можно сгенерировать исключение. Использование PHP try catch в примере:

 > //Перехватываем (catch) исключение, если что-то идет не так. catch (Exception $ex) < //Выводим сообщение об исключении. echo $ex->getMessage(); >

В приведенном выше примере использования в PHP try exception мы генерируем исключение тогда, когда не можем открыть запрашиваемый файл. И генерируем мы его, так как файл должен был существовать. Примеры ситуаций, когда вы можете генерировать исключения:

  1. Ваше PHP-приложение не может подключиться к MySQL ;
  2. Ошибка при запросе к базе данных;
  3. Ошибка при запросе к API ;
  4. Получен некорректный тип запроса;
  5. Отсутствуют необходимые переменные $_POST или $_GET .

Нужно ли перехватывать все исключения?

Лично я так не считаю. К примеру: вам не удается подключиться к базе данных, и генерируется исключение, нужно ли тогда исполнять весь оставшийся код? Если весь последующий код привязан к базе данных, которой просто не существует, то зачем же его исполнять?

По моему мнению, исключения нужно перехватывать с помощью PHP try catch finally только, если это не оказывает негативного влияния на остальные функции приложения.

Например: если API-запрос к внешнему сервису выдает ошибку, то вы можете перехватить исключение и вывести дружественное пользователю сообщение « Невозможно подключиться к базе данных » или « Информация о погоде недоступна ».

Не перехваченные исключения следует обрабатывать с помощью пользовательского обработчика. Так вы сможете обрабатывать не перехваченные исключения, и выводить понятные человеку сообщения или заглушки.

Обработка исключений

В процессе работы программы могут возникать различные ошибки, которые могут прервать работу программы. Например, рассмотрим следующую ситуацию:

$a = 5; $b = 0; $result = $a / $b; echo $result; echo "Конец работы программы";

Программа выводит результат деления. Поскольку делитель равен 0, а на ноль делить нельзя, то при выполнении деления программа завершится, и в браузере мы увидим что-то типа следующего:

Fatal error: Uncaught DivisionByZeroError: Division by zero in D:\localhost\hello.php:11 Stack trace: #0 thrown in D:\localhost\hello.php on line 11

Браузер отобразит нам произошедшую ошибку, причем дальше после строки с делением программа даже не будет выполняться.

Кто-то может сказать, что ситуация искуственная, так как мы сами определили делитель равный нулю. Но данные могут передаваться извне. Кроме того, кроме деления на ноль есть различные ситуации, при которых могут происходить ошибки. Но PHP предоставляет ряд возможностей для обработки подобных ситуаций.

Для обработки исключений в PHP применяется конструкция try-catch :

try < // код, который может вызвать исключение >catch(Тип_исключения $ex) < // обработка исключения >

Эта конструкция в общем варианте состоит из двух блоков — try и catch . В блок try помещается код, который потенциально может вызвать исключение. А в блоке catch помещается обработка возникшего исключения. Причем каждого типа исключения мы можем определить свою логику обработки. Конкретный тип исключения, который мы хотим обработать, указывается в круглых скобках после оператора catch :

catch(Тип_исключения $ex)

После названия типа указывается переменная этого типа (в данном случае $ex ), которая будет хранить информацию об исключении и которую мы можем использовать при обработке исключения.

Если в блоке try при выполнении кода возникает ошибка, то блок try прекращает выполнение и передает управление блоку catch , который обрабатывает ошибку. А после завершения выполнения кода в блоке catch программа продолжает выполнять инструкции, которые размещены после блока catch .

Если в блоке try при выполнении кода не возникает ошибок, то блок catch не выполняется, а после завершения блока try программа продолжает выполнять инструкции, которые размещены после блока catch .

Например, обработаем ошибку с делением на ноль:

try < // код, который может вызвать исключение $a = 5; $b = 0; $result = $a / $b; echo $result; >catch(DivisionByZeroError $ex) < // обработка исключения echo "Произошло исключение:
"; echo $ex . "
"; > echo "Конец работы программы";

В данном случае код деления на ноль, поскольку он может потенциально вызвать ошибку, помещен в блок try .

В блоке catch обрабатывается ошибка типа DivisionByZeroError , которая генерируется при делении на ноль. Вся обработка сводится к выводу информации на экран.

В итоге при выполнении программа выведет следующее:

Произошло исключение: DivisionByZeroError: Division by zero in D:\localhost\hello.php:14 Stack trace: #0 Конец работы программы

Как видно из вывода программы, она не завершается аварийно при делении на ноль, а продолжает работу.

Типы ошибок и исключений

В PHP для разных ситуаций есть множество типов, которые описывают ошибки. Все эти встроенные типы применяют интерфейс Throwable :

Ошибки и исключения Error, Exception и Throwable в PHP

Все типы делятся на две группы: собственно ошибки (класс Error ) и собственно исключения (класс Exception ). А от классов Error и Exception наследуются классы ошибок и исключений, которые описывают конкретные ситуации. Например, от класса Error наследуется класс ArithmeticError , который описывает ошибки, возникающие при выполнении арифметических операций. А от класса ArithmeticError наследуется класс DivisionByZeroError , который представляют ошибку при делении на ноль.

Блок catch

Конструкция try..catch позволяет определить несколько блоков catch — для обработки различных типов ошибок и исключений:

try < $result = 5 / 0; echo $result; >catch(ParseError $p) < echo "Произошла ошибка парсинга"; >catch(DivisionByZeroError $d)

При возникновении ошибки будет для ее обработки будет выбираться тот блок catch , который соответствует вошникшей ошибки. Так, в данном случае при делении на ноль будет выполняться второй блок catch .

Если бы в блоке try возникла бы ошибка, которая бы не соответствовала типам из блоков catch (в данном случае — типам DivisionByZeroError и ParseError), то такая ошибка не была бы обработана, и соответственно программа бы аварийно завершила свое выполнение.

Блоки catch с более конкретными типами ошибок и исключений должны идти в начале, а более с более общими типа — в конце:

try < $result = 5 / 0; echo $result; >catch(DivisionByZeroError $ex) < echo "На ноль делить нельзя"; >catch(ArithmeticError $ex) < echo "Ошибка при выполнении арифметической операции"; >catch(Error $ex) < echo "Произошла ошибка"; >catch(Throwable $ex)

Класс DivisionByZeroError унаследован от ArithmeticError, который, в свою очередь, унаследован от Error, реализующего интерфейс Throwable. Поэтому класс DivisionByZeroError представляет более конкретный тип и представляемые им ошибки должны обрабатываться в первую очередь. А тип Throwable представляет наиболее общий тип, так как ему соответствуют все возможные ошибки и исключения, поэтому блоки catch с таким типом должны идти в конце.

В данном случае опять же в блоке try происходит ошибка деления на ноль. Но этой ошибке соответствуют все четыре блока catch . Для обработки PHP будет выбирать первый попавшийся, который соответствует типу ошибки. В данном случае это блок для обработки ошибки типа DivisionByZeroError.

Если нам надо обрабатывать в принципе все ошибки и исключения, то мы можем определить только обработку общего для всех них типа Throwable:

try < $result = 5 / 0; echo $result; >catch(Throwable $ex)

Начиная с версии PHP 8.0 в блоке catch можно просто указать тип обрабатываемого исключения, не определяя переменную:

catch(DivisionByZeroError)

Получение информации об ошибках и исключениях

Интерфейс Throwable предоставляет ряд методов, которые позволяют получить некоторую информацию о возникшем исключении:

  • getMessage() : возвращает сообщение об ошибке
  • getCode() : возвращает код исключения
  • getFile() : возвращает название файла, в котором возникла ошибка
  • getLine() : возвращает номер строки, в которой возникла ошибка
  • getTrace() : возвращает трассировку стека
  • getTraceAsString() : возвращает трассировку стека в виде строки

Применим некоторые из этих методов:

try < $result = 5 / 0; echo $result; >catch(DivisionByZeroError $ex) < echo "Сообщение об ошибке: " . $ex->getMessage() . "
"; echo "Файл: " . $ex->getFile() . "
"; echo "Номер строки: " . $ex->getLine() . "
"; >
Сообщение об ошибке: Division by zero Файл: D:\localhost\hello.php Номер строки: 11

Блок finally

Конструкция try..catch также может определять блок finally . Этот блок выполняется в конце — после блока try и catch вне зависимости, возникла или нет ошибка. Нередко блок finally используется для закрытия ресурсов, которые применяются в блоке try.

try < $result = 5 / 0; echo $result . "
"; > catch(Throwable $ex) < echo "Ошибка при выполнении программы
"; > finally < echo "Блок finally
"; > echo "Конец работы программы";
Ошибка при выполнении программы Блок finally Конец работы программы

Конструкция try..catch..finally может содержать либо все три блока, либо только два блока try и либо блок catch , либо блок finally .

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

Недавно мне попался проект, где добрая часть кода составляла обработка исключений. То есть создавалось впечатление, что автор хотел предусмотреть чуть ли не все возможные варианты. Хуже всего было то, что логика приложения была сама по себе завязана на эти самые исключения. То есть, вместо элементарной проверки входных данных кидалось исключение.

Пользоваться таким кодом очень сложно, поскольку приходится заключать каждую функцию/метод в блок try/catch, хотя было бы достаточно, если функция вернёт обычный false .

Мне кажется, что основная причина такого использования исключений в том, что у php-программистов нет понимания зачем исключения вообще нужны, а также в том, что их часто путают с обработкой ошибок.

Для новичков, которые не имеют опыта полноценного программирования (например на Pascal/Delphi) исключения — это вообще тёмный лес. Попробуем разобраться.

Что такое «исключения»?

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

Так вот под «исключением» понимается обработка исключительных ситуаций. Простой пример из «больших» языков.

Считывается файл в какую-то переменную. Перед этим для этого выделяется блок памяти. В процессе чтения файла, возникает исключительная ситуация, например файл удалили или он заблокирован на чтение. Данная программа аварийно завершает свою работу, а значит участок памяти не был освобождён.

Поэтому такие критически важные участки кода оборачивают в специальный блок, который позволяет программе продолжить работу в случае «вылета».

На Паскале это может выглядеть так:

try TreeView.LoadFromStream(stream); except showmessage('Ошибка загрузки потока. Что-то с системой. '); end;

В данном примере блок try — это т.н. защищённый блок кода — если в нём возникнет исключение, то управление будет передано в блок except — это обработчик исключения.

В другом примере используется блок finally .

FIniFile := TRegIniFile.Create(s); try FIniFile.ReadString('', 'data',''); . finally FIniFile.Free; end;

Здесь переменная FIniFile считывает некие данные, но если они ошибочные, то возникает исключение. Но здесь главная задача — это корректно освободить память и это происходит в блоке finally , который гарантированно сработает как при возникновении исключения, так и без него.

Исключительных ситуаций на самом деле не так много, как может показаться. Обычно это работа с файлами, потоками, то есть то, что невозможно заранее предусмотреть в самой программе.

Исключения в PHP

В PHP исключения реализуются похожим образом.

try < . защищённый участок кода . >catch (Exception $e)

Или вариант с finally :

try < . защищённый участок кода . >catch (Exception $e) < . код, если возникла исключительная ситуация . >finally

При этом можно использовать как try/catch/finally , так и try/finally .

Казалось бы всё просто, но как обычно в PHP есть нюансы.

«Спотыкачка» для новичков

Рассмотрим простой пример. Есть функция для деления двух чисел.

function my1($a, $b) < try < $r = $a / $b; >catch (Exception $e) < $r = 'Нельзя делить на ноль'; >return $r; > echo my1(1, 0);

Мы ожидаем, что выполнение этого выведет «Нельзя делить на ноль». Однако на самом деле мы получаем сообщение от PHP «Warning: Division by zero». То есть блок catch не сработал.

Почему так происходит?

Не все ошибки — исключения

В PHP все ошибки имеют тип в виде предопределённых констант: E_ERROR, E_WARNING, E_NOTICE и т.д.

Так вот ошибки E_WARNING не являются исключительной ситуацией, поскольку программа может продолжить своё выполнение дальше. Если после echo my1(1, 0); мы разместим ещё какой-то код, то PHP его выполнит.

Это важное понимание того, что такое исключения — отслеживание аварийных ситуаций, чтобы продолжить дальнейшее выполнение программы.

То есть когда мы пытаемся поделить число на ноль, то это всего лишь некорректная обработка входящих данных, которая не может привести к полному краху.

Обработка данных

PHP в первую очередь это процедурное программирование, где функции самостоятельно умеют обрабатывать входящие данные. Хороший пример это функции для текста, например strpos() , которая возвращает не только позицию вхождения, но и FALSE, если его нет.

То есть смысл в том, что в PHP большинство функций возвращают какое-то значение или хотя бы код ошибки, вместо того, чтобы аварийно завершить программу. Поэтому при программировании на PHP нужно стараться следовать именно этой же логике.

Наш пример можно было бы переписать так:

function my2($a, $b) < if ($b == 0) return false; // 'Нельзя делить на ноль'; else return $a / $b; >if ($r = my2(1, 0) !== FALSE) echo $r; else echo 'Ошибка';

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

Как не нужно делать

Плохая практика — вместо обработки входящих данных, генерировать исключение. В PHP можно сгенерировать исключение вручную с помощью throw .

function my3($a, $b) < if ($b === 0) throw new Exception('Нельзя делить на ноль'); else return $a / $b; >try < echo my3(1, 0); >catch (Exception $e) < echo $e->getMessage(); >

Здесь, если $b равен нулю, генерируется исключение. Проблема здесь в том, что использовать такую функцию приходится только в блоке try/catch, что не только усложнят код, но и смешивает логику приложения с исключениями. Причём этот пример достаточно простой: если копнуть глубже, то исключения могут быть разных типов (классов) и их «ветвистая» обработка безумно усложняет код.

Правильны подход

Функция должна сама обрабатывать свои исключения.

function my4($a, $b) < try < if ($b === 0) throw new Exception('Нельзя делить на ноль'); else return $a / $b; >catch (Exception $e) < return $e->getMessage(); > > echo my4(1, 0);

Если $b равен нулю, генерируется исключение, которое тут же ловится в блоке catch . Это как бы совмещённый подход, когда функция и проверяет данные, и генерирует исключения. Поскольку можно отследить разные ситуации, то можно сгенерировать и разные исключения. Например если в функцию будет передано не число, а строка или другой нечисловой тип данных, то возвратить либо сообщение об ошибке, либо просто 0.

Как отследить WARNING

Важно понимать, что в PHP ошибки и исключения — разные вещи и работают они по разному. По большому счёту можно вообще обойтись без исключений, поскольку функции PHP сами по себе неплохо справляются с нештатными ситуациями.

Однако в PHP есть механизм по «превращению» ошибок в исключения. Для этого следует переопределить стандартный обработчик ошибок с помощью функции set_error_handler() .

function exception_error_handler($severity, $message, $file, $line) < if (!(error_reporting() & $severity)) < // Этот код ошибки не входит в error_reporting return; >throw new ErrorException($message, 0, $severity, $file, $line); > set_error_handler('exception_error_handler');

После этого мы можем вызвать самый первый пример с my1()

echo my1(1, 0);

и получить как и ожидаем: «Нельзя делить на ноль».

Выводы

По возможности нужно стараться использовать проверку и обработку данных, вместо генерации исключений. PHP достаточно развитый язык, где можно проверять что угодно на корректность.

Если функция генерирует исключение, то лучше всего, если она сама же его и обработает.

Плохо, если функция только генерирует исключение в надежде, что его «кто-то» «где-то» словит. И совсем плохо, если логика функции/приложения построена на исключениях. Такой подход негативно сказывается как на логике работы, так и на общем объёме кода. Разобраться в нём будет проблематично.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *