Обработка исключений
В процессе работы программы могут возникать различные ошибки, которые могут прервать работу программы. Например, рассмотрим следующую ситуацию:
$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 ). А от классов 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 .
Блок catch не срабатывает (PHP, конструкция try catch)
У меня конечно элементарный вопрос, но вот никак не могу понять принцип работы конструкции try-catch на php. В общем имеем максимально простой код, как внизу. Функция checkNum($number) проверяет является ли число больше или меньше 5. Функция выполняется в блоке try. Если в блоке try мы нарочно сделаем какую-то ошибку (например назовем функцию checkNNNums, которой не существует), то это по идее должно приводить к исключению и выполнению блока catch. Действительно появляется ошибка, но код, который в блоке catch не срабатывает, т.е. например мы не видим заложенной в него надписи ‘Сработал блок Catch — Сообщение: ‘, а просто получаем Fatal error: Uncaught Error: Call to undefined function checkNums() Мне нужно отработать именно try-catch, понимаю, что есть варианты решения через альтернативные варианты, но нужно именно таким образом. PHP 7.3 Код ниже:
5) < echo "число больше 5"; >else < echo "число меньше 5"; >> try < checkNum(7); echo 'Сработал блок try'; >catch(Exception $e) < echo 'Сработал блок Catch - Сообщение: ' .$e->getMessage(); > ?>
Отслеживать
user285292
задан 6 янв 2020 в 19:11
11 4 4 бронзовых знака
2 ответа 2
Сортировка: Сброс на вариант по умолчанию
Если в блоке try мы нарочно сделаем какую-то ошибку (например назовем функцию checkNNNums, которой не существует), то это по идее должно приводить к исключению
Совершенно верно для вашей версии PHP. (не уверен в своей памяти и быстро проверить не удалось, вероятно для всех PHP 7.0 и новее)
Ваше заблуждение и ошибка в том, что вы ожидаете генерацию исключения класса Exception . Возможно вы считаете что все исключения наследованы от Exception . И как раз вот это неверно. Исключение в этом случае генерируется класса Error и этот класс в своём дереве наследования не опирается на Exception . Дерево наследования исключений в PHP7.0 и новее выглядит так:
Throwable Error ArithmeticError DivisionByZeroError AssertionError CompileError ParseError TypeError ArgumentCountError Exception
И если вы замените свой catch на:
catch(\Throwable $e)
То он уже будет ловить эту ошибку в вашей версии PHP.
Тем не менее обратите внимание, что не все ошибки PHP заменены на генерацию исключений. Механизм ошибок и исключений не связаны друг с другом.
Исключения
В PHP реализована модель исключений, аналогичная тем, что используются в других языках программирования. Исключение в PHP может быть выброшено ( throw ) и поймано ( catch ). Код может быть заключён в блок try , чтобы облегчить обработку потенциальных исключений. У каждого блока try должен быть как минимум один соответствующий блок catch или finally .
Если выброшено исключение, а в текущей области видимости функции нет блока catch , исключение будет "подниматься" по стеку вызовов к вызывающей функции, пока не найдёт подходящий блок catch . Все блоки finally , которые встретятся на этом пути, будут выполнены. Если стек вызовов разворачивается до глобальной области видимости, не встречая подходящего блока catch , программа завершается с неисправимой ошибкой, если не был установлен глобальный обработчик исключений.
Выброшенный объект должен наследовать ( instanceof ) интерфейс Throwable . Попытка выбросить объект, который таковым не является, приведёт к неисправимой ошибке PHP.
Начиная с PHP 8.0.0, ключевое слово throw является выражением и может быть использовано в любом контексте выражения. В предыдущих версиях оно было утверждением и должно было располагаться в отдельной строке.
catch
Блок catch определяет, как реагировать на выброшенное исключение. Блок catch определяет один или несколько типов исключений или ошибок, которые он может обработать, и, по желанию, переменную, которой можно присвоить исключение (указание переменной было обязательно до версии PHP 8.0.0). Первый блок catch , с которым столкнётся выброшенное исключение или ошибка и соответствует типу выброшенного объекта, обработает объект.
Несколько блоков catch могут быть использованы для перехвата различных классов исключений. Нормальное выполнение (когда исключение не выброшено в блоке try ) будет продолжаться после последнего блока catch , определённого в последовательности. Исключения могут быть выброшены ( throw ) (или повторно выброшены) внутри блока catch . В противном случае выполнение будет продолжено после блока catch , который был вызван.
При возникновении исключения, код, следующий за утверждением, не будет выполнен, а PHP попытается найти первый подходящий блок catch . Если исключение не поймано, будет выдана неисправимая ошибка PHP с сообщением " Uncaught Exception . ", если только обработчик не был определён с помощью функции set_exception_handler() .
Начиная с версии PHP 7.1.0, в блоке catch можно указывать несколько исключений, используя символ | . Это полезно, когда разные исключения из разных иерархий классов обрабатываются одинаково.
Начиная с версии PHP 8.0.0, имя переменной для пойманного исключения является необязательным. Если оно не указано, блок catch будет выполнен, но не будет иметь доступа к выброшенному объекту.
finally
Блок finally также может быть указан после или вместо блоков catch . Код в блоке finally всегда будет выполняться после блоков try и catch , независимо от того, было ли выброшено исключение и до возобновления нормального выполнения.
Одно из заметных взаимодействий происходит между блоком finally и оператором return . Если оператор return встречается внутри блоков try или catch , блок finally всё равно будет выполнен. Более того, оператор return выполнится, когда встретится, но результат будет возвращён после выполнения блока finally . Кроме того, если блок finally также содержит оператор return , возвращается значение из блока finally .
Глобальный обработчик исключений
Если исключению разрешено распространяться на глобальную область видимости, оно может быть перехвачено глобальным обработчиком исключений, если он установлен. Функция set_exception_handler() может задать функцию, которая будет вызвана вместо блока catch , если не будет вызван никакой другой блок. Эффект по сути такой же, как если бы вся программа была обёрнута в блок try - catch с этой функцией в качестве catch .
Примечания
Замечание:
Внутренние функции PHP в основном используют отчёт об ошибках, только современные объектно-ориентированные модули используют исключения. Однако ошибки можно легко перевести в исключения с помощью класса ErrorException. Однако эта техника работает только с исправляемыми ошибками.
Пример #1 Преобразование отчётов об ошибках в исключения
function exceptions_error_handler ( $severity , $message , $filename , $lineno ) throw new ErrorException ( $message , 0 , $severity , $filename , $lineno );
>
?php
Подсказка
Примеры
Пример #2 Выбрасывание исключения
function inverse ( $x ) if (! $x ) throw new Exception ( 'Деление на ноль.' );
>
return 1 / $x ;
>
?php
try echo inverse ( 5 ) . "\n" ;
echo inverse ( 0 ) . "\n" ;
> catch ( Exception $e ) echo 'Выброшено исключение: ' , $e -> getMessage (), "\n" ;
>
// Продолжение выполнения
echo "Привет, мир\n" ;
?>
Результат выполнения данного примера:
0.2 Выброшено исключение: Деление на ноль. Привет, мир
Пример #3 Обработка исключений с помощью блока finally
function inverse ( $x ) if (! $x ) throw new Exception ( 'Деление на ноль.' );
>
return 1 / $x ;
>
?php
try echo inverse ( 5 ) . "\n" ;
> catch ( Exception $e ) echo 'Поймано исключение: ' , $e -> getMessage (), "\n" ;
> finally echo "Первый блок finally.\n" ;
>
try echo inverse ( 0 ) . "\n" ;
> catch ( Exception $e ) echo 'Поймано исключение: ' , $e -> getMessage (), "\n" ;
> finally echo "Второй блок finally.\n" ;
>
// Продолжение нормального выполнения
echo "Привет, мир\n" ;
?>
Результат выполнения данного примера:
0.2 Первый блок finally. Поймано исключение: Деление на ноль. Второй блок finally. Привет, мир
Пример #4 Взаимодействие между блоками finally и return
function test () try throw new Exception ( 'foo' );
> catch ( Exception $e ) return 'catch' ;
> finally return 'finally' ;
>
>
Результат выполнения данного примера:
finally
Пример #5 Вложенные исключения
class MyException extends Exception
class Test public function testing () try try throw new MyException ( 'foo!' );
> catch ( MyException $e ) // повторный выброс исключения
throw $e ;
>
> catch ( Exception $e ) var_dump ( $e -> getMessage ());
>
>
>
$foo = new Test ;
$foo -> testing ();
Результат выполнения данного примера:
string(4) "foo!"
Пример #6 Обработка нескольких исключений в одном блоке catch
class MyException extends Exception
class MyOtherException extends Exception
class Test public function testing () try throw new MyException ();
> catch ( MyException | MyOtherException $e ) var_dump ( get_class ( $e ));
>
>
>
$foo = new Test ;
$foo -> testing ();
Результат выполнения данного примера:
string(11) "MyException"
Пример #7 Пример блока catch без указания переменной
Допустимо начиная с PHP 8.0.0
class SpecificException extends Exception <>
function test () throw new SpecificException ( 'Ой!' );
>
try test ();
> catch ( SpecificException ) print "Было поймано исключение SpecificException, но нам безразлично, что у него внутри." ;
>
?>
Пример #8 Throw как выражение
Допустимо начиная с PHP 8.0.0
function test () do_something_risky () or throw new Exception ( 'Всё сломалось' );
>
try test ();
> catch ( Exception $e ) print $e -> getMessage ();
>
?>
User Contributed Notes 14 notes
14 years ago
If you intend on creating a lot of custom exceptions, you may find this code useful. I've created an interface and an abstract exception class that ensures that all parts of the built-in Exception class are preserved in child classes. It also properly pushes all information back to the parent constructor ensuring that nothing is lost. This allows you to quickly create new exceptions on the fly. It also overrides the default __toString method with a more thorough one.
interface IException
/* Protected methods inherited from Exception class */
public function getMessage (); // Exception message
public function getCode (); // User-defined Exception code
public function getFile (); // Source filename
public function getLine (); // Source line
public function getTrace (); // An array of the backtrace()
public function getTraceAsString (); // Formated string of trace
/* Overrideable methods inherited from Exception class */
public function __toString (); // formated string for display
public function __construct ( $message = null , $code = 0 );
>
abstract class CustomException extends Exception implements IException
protected $message = 'Unknown exception' ; // Exception message
private $string ; // Unknown
protected $code = 0 ; // User-defined exception code
protected $file ; // Source filename of exception
protected $line ; // Source line of exception
private $trace ; // Unknown
public function __construct ( $message = null , $code = 0 )
if (! $message ) throw new $this ( 'Unknown ' . get_class ( $this ));
>
parent :: __construct ( $message , $code );
>
public function __toString ()
return get_class ( $this ) . " ' < $this ->message > ' in < $this ->file > ( < $this ->line > )\n"
. " < $this ->getTraceAsString ()> " ;
>
>
?>
Now you can create new exceptions in one line:
class TestException extends CustomException <>
?>
Here's a test that shows that all information is properly preserved throughout the backtrace.
function exceptionTest ()
try throw new TestException ();
>
catch ( TestException $e ) echo "Caught TestException (' < $e ->getMessage ()> ')\n < $e >\n" ;
>
catch ( Exception $e ) echo "Caught Exception (' < $e ->getMessage ()> ')\n < $e >\n" ;
>
>
echo '
' . exceptionTest () . '
' ;
?>
Here's a sample output:
Caught TestException ('Unknown TestException')
TestException 'Unknown TestException' in C:\xampp\htdocs\CustomException\CustomException.php(31)
#0 C:\xampp\htdocs\CustomException\ExceptionTest.php(19): CustomException->__construct()
#1 C:\xampp\htdocs\CustomException\ExceptionTest.php(43): exceptionTest()
#2
Try Catch PHP или исключения в PHP
Представляем вашему вниманию руководство для новичков о том, как использовать блоки Try Catch PHP . В этой статье я покажу, как перехватывать исключения.
Давайте сразу взглянем на пример сгенерированного исключения ( и впоследствии перехваченного ):
> //Перехватываем (catch) исключение, если что-то идет не так. catch (Exception $ex) < //Выводим сообщение об исключении. echo $ex->getMessage(); >
В приведенном выше примере я продемонстрировал использование TRY и CATCH , в котором исключение всегда сгенерировано ( только ради примера ):
- Внутри блока TRY мы проверяем, равняется ли цифра 1 цифре 2 . Так как она не равняется ( и никогда не будет равняться ), мы генерируем исключение с сообщением “ 1 не равняется 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 мы генерируем исключение тогда, когда не можем открыть запрашиваемый файл. И генерируем мы его, так как файл должен был существовать. Примеры ситуаций, когда вы можете генерировать исключения:
- Ваше PHP-приложение не может подключиться к MySQL ;
- Ошибка при запросе к базе данных;
- Ошибка при запросе к API ;
- Получен некорректный тип запроса;
- Отсутствуют необходимые переменные $_POST или $_GET .
Нужно ли перехватывать все исключения?
Лично я так не считаю. К примеру: вам не удается подключиться к базе данных, и генерируется исключение, нужно ли тогда исполнять весь оставшийся код? Если весь последующий код привязан к базе данных, которой просто не существует, то зачем же его исполнять?
По моему мнению, исключения нужно перехватывать с помощью PHP try catch finally только, если это не оказывает негативного влияния на остальные функции приложения.
Например: если API-запрос к внешнему сервису выдает ошибку, то вы можете перехватить исключение и вывести дружественное пользователю сообщение « Невозможно подключиться к базе данных » или « Информация о погоде недоступна ».
Не перехваченные исключения следует обрабатывать с помощью пользовательского обработчика. Так вы сможете обрабатывать не перехваченные исключения, и выводить понятные человеку сообщения или заглушки.