Обработка исключений
В процессе работы программы могут возникать различные ошибки, которые могут прервать работу программы. Например, рассмотрим следующую ситуацию:
$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 .
Что такое try catch в PHP?
Блок try-catch в языке программирования PHP предоставляет механизм для обработки исключений, то есть ошибок, которые могут возникнуть в ходе выполнения кода. Этот механизм позволяет программисту более контролируемо и элегантно обрабатывать исключительные ситуации.
Пример использования try-catch:
catch (Exception $e) < // Обработка исключения echo "Произошло исключение: " . $e->getMessage(); > ?>
Основные аспекты использования try-catch:
-
Блок try: Внутри блока try помещается код, который может вызвать исключение.
catch (Exception $e) < // Обработка исключения // . >
catch (Exception $e) < // Обработка исключения echo "Произошло исключение: " . $e->getMessage(); >
Работа с исключениями в PHP
Сегодня мы с вами разберем такую тему как исключения в PHP. Но прежде чем перейти к деталям, давайте дадим простое понятие термину «Исключительная ситуация». Исключительная ситуация в программе – это ситуация, при которой дальнейшее выполнение кода не имеет смысла. Например, новости на страничке /post/add могут добавлять только администраторы, но при этом это пытается сделать неавторизованный пользователь. Здесь имеет смысл проверить в самом начале права пользователя и если их недостаточно, обработать эту исключительную ситуацию. Или вот ещё один пример: наше приложение работает с базой данных, но при подключении к серверу MySQL обнаруживается, что такой базы данных на сервере нет. При такой ситуации тоже не имеет смысла продолжать выполнение скрипта – это исключительная ситуация.
Продолжение урока будет доступно вам
после покупки ООП в PHP
Обработка исключений и ошибок в 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 достаточно развитый язык, где можно проверять что угодно на корректность.
Если функция генерирует исключение, то лучше всего, если она сама же его и обработает.
Плохо, если функция только генерирует исключение в надежде, что его «кто-то» «где-то» словит. И совсем плохо, если логика функции/приложения построена на исключениях. Такой подход негативно сказывается как на логике работы, так и на общем объёме кода. Разобраться в нём будет проблематично.