Исключения
Содержание
- Наследование исключений
В 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 Преобразование отчётов об ошибках в исключения
<?php
function exceptions_error_handler($severity, $message, $filename, $lineno) {
throw new ErrorException($message, 0, $severity, $filename, $lineno);
}set_error_handler('exceptions_error_handler');
?>
Примеры
Пример #2 Выбрасывание исключения
<?php
function inverse($x) {
if (!$x) {
throw new Exception('Деление на ноль.');
}
return 1/$x;
}
try {
echo
inverse(5) . "\n";
echo inverse(0) . "\n";
} catch (Exception $e) {
echo 'Выброшено исключение: ', $e->getMessage(), "\n";
}// Продолжение выполнения
echo "Привет, мир\n";
?>
Результат выполнения данного примера:
0.2 Выброшено исключение: Деление на ноль. Привет, мир
Пример #3 Обработка исключений с помощью блока finally
<?php
function inverse($x) {
if (!$x) {
throw new Exception('Деление на ноль.');
}
return 1/$x;
}
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
<?phpfunction test() {
try {
throw new Exception('foo');
} catch (Exception $e) {
return 'catch';
} finally {
return 'finally';
}
}
echo
test();
?>
Результат выполнения данного примера:
Пример #5 Вложенные исключения
<?phpclass 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();?>
Результат выполнения данного примера:
Пример #6 Обработка нескольких исключений в одном блоке catch
<?phpclass 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();?>
Результат выполнения данного примера:
Пример #7 Пример блока catch
без указания переменной
Допустимо начиная с PHP 8.0.0
<?phpclass SpecificException extends Exception {}
function
test() {
throw new SpecificException('Ой!');
}
try {
test();
} catch (SpecificException) {
print "Было поймано исключение SpecificException, но нам безразлично, что у него внутри.";
}
?>
Пример #8 Throw как выражение
Допустимо начиная с PHP 8.0.0
<?phpfunction test() {
do_something_risky() or throw new Exception('Всё сломалось');
}
try {
test();
} catch (Exception $e) {
print $e->getMessage();
}
?>
ask at nilpo dot com ¶
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.
<?php
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; // Unknownpublic 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:
<?php
class TestException extends CustomException {}
?>
Here's a test that shows that all information is properly preserved throughout the backtrace.
<?php
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
'<pre>' . exceptionTest() . '</pre>';
?>
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 {main}
Johan ¶
12 years ago
Custom error handling on entire pages can avoid half rendered pages for the users:
<?php
ob_start();
try {
/*contains all page logic
and throws error if needed*/
...
} catch (Exception $e) {
ob_end_clean();
displayErrorPage($e->getMessage());
}
?>
christof+php[AT]insypro.com ¶
6 years ago
In case your E_WARNING type of errors aren't catchable with try/catch you can change them to another type of error like this:
<?php
set_error_handler(function($errno, $errstr, $errfile, $errline){
if($errno === E_WARNING){
// make it more serious than a warning so it can be caught
trigger_error($errstr, E_ERROR);
return true;
} else {
// fallback to default php error handler
return false;
}
});
try {
// code that might result in a E_WARNING
} catch(Exception $e){
// code to handle the E_WARNING (it's actually changed to E_ERROR at this point)
} finally {
restore_error_handler();
}
?>
Shot (Piotr Szotkowski) ¶
14 years ago
‘Normal execution (when no exception is thrown within the try block, *or when a catch matching the thrown exception’s class is not present*) will continue after that last catch block defined in sequence.’
‘If an exception is not caught, a PHP Fatal Error will be issued with an “Uncaught Exception …” message, unless a handler has been defined with set_exception_handler().’
These two sentences seem a bit contradicting about what happens ‘when a catch matching the thrown exception’s class is not present’ (and the second sentence is actually correct).
Edu ¶
10 years ago
The "finally" block can change the exception that has been throw by the catch block.
<?php
try{
try {
throw new \Exception("Hello");
} catch(\Exception $e) {
echo $e->getMessage()." catch in\n";
throw $e;
} finally {
echo $e->getMessage()." finally \n";
throw new \Exception("Bye");
}
} catch (\Exception $e) {
echo $e->getMessage()." catch out\n";
}
?>
The output is:
Hello catch in
Hello finally
Bye catch out
daviddlowe dot flimm at gmail dot com ¶
5 years ago
Starting in PHP 7, the classes Exception and Error both implement the Throwable interface. This means, if you want to catch both Error instances and Exception instances, you should catch Throwable objects, like this:
<?phptry {
throw new Error( "foobar" );
// or:
// throw new Exception( "foobar" );
}
catch (Throwable $e) {
var_export( $e );
}?>
Simo ¶
8 years ago
#3 is not a good example. inverse("0a") would not be caught since (bool) "0a" returns true, yet 1/"0a" casts the string to integer zero and attempts to perform the calculation.
mlaopane at gmail dot com ¶
5 years ago
<?php/**
* You can catch exceptions thrown in a deep level function
*/function employee()
{
throw new \Exception("I am just an employee !");
}
function
manager()
{
employee();
}
function
boss()
{
try {
manager();
} catch (\Exception $e) {
echo $e->getMessage();
}
}boss(); // output: "I am just an employee !"
telefoontoestel at nospam dot org ¶
9 years ago
When using finally keep in mind that when a exit/die statement is used in the catch block it will NOT go through the finally block.
<?php
try {
echo "try block<br />";
throw new Exception("test");
} catch (Exception $ex) {
echo "catch block<br />";
} finally {
echo "finally block<br />";
}// try block
// catch block
// finally block
?>
<?php
try {
echo "try block<br />";
throw new Exception("test");
} catch (Exception $ex) {
echo "catch block<br />";
exit(1);
} finally {
echo "finally block<br />";
}// try block
// catch block
?>
Tom Polomsk ¶
8 years ago
Contrary to the documentation it is possible in PHP 5.5 and higher use only try-finally blocks without any catch block.
tianyiw at vip dot qq dot com ¶
14 days ago
Easy to understand `finally`.
<?php
try {
try {
echo "before\n";
1 / 0;
echo "after\n";
} finally {
echo "finally\n";
}
} catch (\Throwable) {
echo "exception\n";
}
?>
# Print:
before
finally
exception
Sawsan ¶
11 years ago
the following is an example of a re-thrown exception and the using of getPrevious function:
<?php
$name
= "Name";//check if the name contains only letters, and does not contain the word nametry
{
try
{
if (preg_match('/[^a-z]/i', $name))
{
throw new Exception("$name contains character other than a-z A-Z");
}
if(strpos(strtolower($name), 'name') !== FALSE)
{
throw new Exception("$name contains the word name");
}
echo "The Name is valid";
}
catch(Exception $e)
{
throw new Exception("insert name again",0,$e);
}
}
catch (
Exception $e)
{
if ($e->getPrevious())
{
echo "The Previous Exception is: ".$e->getPrevious()->getMessage()."<br/>";
}
echo "The Exception is: ".$e->getMessage()."<br/>";
}?>
ilia-yats at ukr dot net ¶
8 months ago
Note some undocumented details about exceptions thrown from 'finally' blocks.
When exception is thrown from 'finally' block, it overrides the original not-caught (or re-thrown) exception. So the behavior is similar to 'return': value returned from 'finally' overrides the one returned earlier. And the original exception is automatically appended to the exceptions chain, i.e. becomes 'previous' for the new one. Example:
<?php
try {
try {
throw new Exception('thrown from try');
} finally {
throw new Exception('thrown from finally');
}
} catch(Exception $e) {
echo $e->getMessage();
echo PHP_EOL;
echo $e->getPrevious()->getMessage();
} // will output:
// thrown from finally
// thrown from try
?>
Example with re-throwing:
<?php
try {
try {
throw new Exception('thrown from try');
} catch (Exception $e) {
throw new Exception('thrown from catch');
} finally {
throw new Exception('thrown from finally');
}
} catch(Exception $e) {
echo $e->getMessage();
echo PHP_EOL;
echo $e->getPrevious()->getMessage();
} // will output:
// thrown from finally
// thrown from catch
?>
The same happens even if explicitly pass null as previous exception:
<?php
try {
try {
throw new Exception('thrown from try');
} finally {
throw new Exception('thrown from finally', null, null);
}
} catch(Exception $e) {
echo $e->getMessage();
echo PHP_EOL;
echo $e->getPrevious()->getMessage();
} // will output:
// thrown from finally
// thrown from try
?>
Also it is possible to pass previous exception explicitly, the 'original' one will be still appended to the chain, e.g.:
<?php
try {
try {
throw new Exception('thrown from try');
} finally {
throw new Exception(
'thrown from finally',
null,
new Exception('Explicitly set previous!')
);
}
} catch(Exception $e) {
echo $e->getMessage();
echo PHP_EOL;
echo $e->getPrevious()->getMessage();
echo PHP_EOL;
echo $e->getPrevious()->getPrevious()->getMessage();
} // will output:
// thrown from finally
// Explicitly set previous!
// thrown from try
?>
This seems to be true for versions 5.6-8.2.
lscorionjs at gmail dot com ¶
8 months ago
<?phptry {
$str = 'hi';
throw new Exception();
} catch (Exception) {
var_dump($str);
} finally {
var_dump($str);
}?>
Output:
string(2) "hi"
string(2) "hi"
() translation by (you can also view the original English article)
В этом посте вы узнаете, как использовать обработку исключений в PHP. Начиная с PHP 5, мы можем использовать блоки try catch для обработки ошибок — это лучший способ обработки исключений и управления потоком вашего приложения. В этой статье мы рассмотрим основы обработки исключений вместе с несколькими примерами из реального мира.
Что такое исключение?
В PHP 5 появилась новая модель ошибок, которая позволяет вам кидать и ловить исключения в вашем приложении — это лучший способ обработки ошибок, чем то, что мы имели в более старых версиях PHP. Все исключения являются экземплярами базового класса Exception
, который мы можем расширить, чтобы ввести наши собственные пользовательские исключения.
Здесь важно отметить, что обработка исключений отличается от обработки ошибок. При обработке ошибок мы можем использовать функцию set_error_handler
для установки нашей настраиваемой функции обработки ошибок, чтобы всякий раз, когда срабатывает ошибка, она вызывала нашу функцию обработки ошибок. Таким образом, вы можете управлять ошибками. Однако, как правило, некоторые виды ошибок не восстанавливаются и прекращают выполнение программы.
С другой стороны, исключения — это что-то, что умышленно вызывает код, и ожидается, что он будет пойман в какой-то момент вашего приложения. Таким образом, мы можем сказать, что исключения восстанавливаются, а не определенные ошибки, которые не подлежат восстановлению. Если исключение, которое выбрасывается, попадает где-то в ваше приложение, выполнение программы продолжается с момента, когда исключение было поймано. А исключение, которое не попадает нигде в ваше приложение, приводит к ошибке, которое останавливает выполнение программы.
Поток управления обработкой исключений
Давайте рассмотрим следующую диаграмму, которая показывает общий поток управления обработкой исключений.
Исключения могут быть выброшены и пойманы с помощью блоков try
и catch
. Вы несете ответственность за выброс исключений, если что-то произойдет, чего не ожидается. Давайте быстро рассмотрим основной поток обработки исключений, как показано в следующем псевдокоде.
1 |
// code before the try-catch block
|
2 |
|
3 |
try { |
4 |
// code
|
5 |
|
6 |
// if something is not as expected
|
7 |
// throw exception using the "throw" keyword
|
8 |
|
9 |
// code, it won't be executed if the above exception is thrown
|
10 |
} catch (Exception $e) { |
11 |
// exception is raised and it'll be handled here
|
12 |
// $e->getMessage() contains the error message
|
13 |
}
|
14 |
|
15 |
// code after the try-catch block, will always be executed
|
В большинстве случаев, когда вы имеете дело с исключениями, вы в конечном итоге используете шаблон, как показано в приведенном выше фрагменте. Вы также можете использовать блок finally
вместе с блоками try
и catch
, но мы вернемся к этому позже в этой статье.
Блок try
— тот, который используется, когда вы подозреваете, что ваш код может генерировать исключение. Вы всегда должны обертывать такой код, используя try
и catch
.
Выброс исключения
Исключение может быть вызвано функцией, которую вы вызываете, или вы можете использовать ключевое слово throw
для выбрасывания исключения вручную. Например, вы можете проверить некоторый ввод перед выполнением любой операции и выбросить исключение, если данные недействительны.
Здесь важно отметить, что если вы выбросите исключение, но вы не определили блок catch
, который должен обрабатывать это исключение, это приведет к фатальной ошибке. Поэтому вам нужно убедиться, что вы всегда определяете блок catch
, если вы бросаете исключения в своем приложении.
Когда исключение попадает в блок catch
, объект Exception
содержит сообщение об ошибке, которое было выбрано с использованием ключевого слова throw
. Переменная $e
в приведенном выше примере является экземпляром класса Exception
, поэтому она имеет доступ ко всем методам этого класса. В этом блоке вы должны определить свою собственную логику обработки исключений — что именно вы хотите сделать с ошибкой, которую вы поймаете.
В следующем разделе мы рассмотрим пример из реального мира, чтобы понять, как работает обработка исключений.
Пример из реального мира
В этом разделе мы построим реальный пример для демонстрации обработки исключений в PHP.
Предположим, что вы создали приложение, которое загружает конфигурацию приложения из файла config.php. Теперь важно, чтобы файл config.php присутствовал, когда ваше приложение загружается. Таким образом, ваше приложение не может работать, если файл config.php отсутствует. Так что это идеальный случай, чтобы выбросить исключение и сообщить пользователю, что им необходимо исправить проблему.
1 |
<?php
|
2 |
try { |
3 |
// init bootstrapping phase
|
4 |
|
5 |
$config_file_path = "config.php"; |
6 |
|
7 |
if (!file_exists($config_file_path)) |
8 |
{
|
9 |
throw new Exception("Configuration file not found."); |
10 |
}
|
11 |
|
12 |
// continue execution of the bootstrapping phase
|
13 |
} catch (Exception $e) { |
14 |
echo $e->getMessage(); |
15 |
die(); |
16 |
}
|
17 |
?>
|
Как вы можете видеть в приведенном выше примере, мы проверяем наличие файла config.php в начале фазы начальной загрузки. Если файл config.php найден, выполнение продолжается в обычном режиме. С другой стороны, мы выбросим исключение, если файл config.php не существует. Кроме того, мы хотели бы прекратить выполнение, если есть исключение!
Вот как вы можете использовать исключения в своих приложениях. Вы должны выбрасывать исключения для исключительных случаев использования — вы не должны излишне бросать исключения для ошибок, таких как недопустимые учетные данные пользователя, неправильные разрешения на доступ к каталогам и т. д., которые вы часто ожидаете. Они лучше обрабатываются общими сообщениями об ошибках в потоке выполнения обычного приложения.
Таким образом, это был пример обработки исключений с использованием класса Exception
по умолчанию. В следующем разделе мы рассмотрим, как вы можете расширить основной класс Exception
и создать свои собственные пользовательские исключения в своем приложении.
Как создавать пользовательские исключения
В этом разделе мы обсудим, как вы можете создавать пользовательские исключения в своих приложениях. Фактически, мы расширим пример, который мы только что обсуждали в предыдущем разделе, чтобы продемонстрировать пользовательские исключения.
В предыдущем примере мы выбрали исключение конфигурации, используя класс Exception
по умолчанию. Это прекрасно, если вы просто хотите иметь дело с сообщением об ошибке. Однако иногда вы хотите сделать немного больше в зависимости от типа исключения, которое бросается. Вот почему пользовательские исключения полезны.
Перейдем к предыдущему примеру, как показано в следующем фрагменте.
1 |
<?php
|
2 |
class ConfigFileNotFoundException extends Exception {} |
3 |
|
4 |
try { |
5 |
// init bootstrapping phase
|
6 |
|
7 |
$config_file_path = "config.php"; |
8 |
|
9 |
if (!file_exists($config_file_path)) |
10 |
{
|
11 |
throw new ConfigFileNotFoundException("Configuration file not found."); |
12 |
}
|
13 |
|
14 |
// continue execution of the bootstrapping phase
|
15 |
} catch (ConfigFileNotFoundException $e) { |
16 |
echo "ConfigFileNotFoundException: ".$e->getMessage(); |
17 |
// other additional actions that you want to carry out for this exception
|
18 |
die(); |
19 |
} catch (Exception $e) { |
20 |
echo $e->getMessage(); |
21 |
die(); |
22 |
}
|
23 |
?>
|
Во-первых, мы определили класс ConfigFileNotFoundException
, который расширяет основной класс Exception
. Теперь он становится нашим настраиваемым классом исключений, и мы можем использовать его, когда хотим выбросить исключение ConfigFileNotFoundException
в нашем приложении.
Затем мы использовали ключевое слово throw
для исключения исключений ConfigFileNotFoundException
в случае, если файл config.php не существует. Однако важное различие находится в блоке catch
. Как вы можете видеть, мы определили два блока catch
, и каждый блок используется для обнаружения различного типа исключения.
Первый получает исключения типа ConfigFileNotFoundException
. Итак, если генерируемое исключение относится к типу ConfigFileNotFoundException
, этот блок будет выполнен. Если тип исключения не соответствует какому-либо конкретному блоку catch
, он будет соответствовать последнему, который должен поймать все генерические сообщения об исключениях.
Блок Finally
В этом разделе мы рассмотрим, как вы можете использовать ключевое слово finally
вместе с блоками try
и catch
. Иногда вы хотите выполнить часть кода независимо от того, было ли исключено исключение. Вот где вы можете использовать блок finally
, поскольку код, который вы размещаете в блоке finally, всегда будет выполняться после выполнения блоков try и catch независимо от того, было ли выбрано исключение.
Попробуем понять это, используя следующий пример.
1 |
try { |
2 |
// code
|
3 |
|
4 |
// if something is not as expected
|
5 |
// throw exception using the "throw" keyword
|
6 |
|
7 |
// code, it won't be executed if the above exception is thrown
|
8 |
} catch (Exception $e) { |
9 |
// exception is raised and it'll be handled here
|
10 |
// $e->getMessage() contains the error message
|
11 |
} finally { |
12 |
// code, it'll always be executed
|
13 |
}
|
Код в приведенном выше примере почти то же самое с единственным исключением, что мы добавили блок finally
после блока catch
. И, как мы обсуждали, код в этом блоке всегда будет выполняться.
Типичные прецеденты, которые мы могли бы придумать для использования блока finally, обычно связаны с очисткой ресурсов. Например, если вы открыли соединение с базой данных или файл на диске в блоке try
, вы можете выполнять задачи очистки, такие как закрытие соединения в блоке finally
, так как это гарантировано.
Обработка исключений — это ключевой навык кодирования, и вы должны подумать над тем, как будут обрабатываться исключения при разработке ваших приложений. Это поможет вам обнаружить и восстановить непредвиденные ошибки в вашем приложении. Надеюсь, что этот пост вдохновит вас написать лучший код обработки ошибок!
Заключение
Сегодня мы обсудили тему обработки исключений в PHP. В первой половине статьи мы обсудили основы исключений в PHP и создали реальный пример, чтобы продемонстрировать, как они работают. В конце мы рассмотрели, как вы можете создавать пользовательские исключения, расширяя основной класс Exception
.
Обработка исключений
Конструкция try catch finally
Последнее обновление: 24.03.2021
В процессе работы программы могут возникать различные ошибки, которые могут прервать работу программы. Например, рассмотрим следующую ситуацию:
$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 {main} 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 "Произошло исключение:<br>"; echo $ex . "<br>"; } echo "Конец работы программы";
В данном случае код деления на ноль, поскольку он может потенциально вызвать ошибку, помещен в блок try
.
В блоке catch
обрабатывается ошибка типа DivisionByZeroError, которая генерируется при делении на ноль. Вся обработка сводится
к выводу информации на экран.
В итоге при выполнении программа выведет следующее:
Произошло исключение: DivisionByZeroError: Division by zero in D:\localhost\hello.php:14 Stack trace: #0 {main} Конец работы программы
Как видно из вывода программы, она не завершается аварийно при делении на ноль, а продолжает работу.
Типы ошибок и исключений
В 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) { echo "На ноль делить нельзя"; }
При возникновении ошибки будет для ее обработки будет выбираться тот блок 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) { echo "Ошибка при выполнении программы"; }
Класс DivisionByZeroError унаследован от ArithmeticError, который, в свою очередь, унаследован от Error, реализующего интерфейс Throwable. Поэтому
класс DivisionByZeroError представляет более конкретный тип и представляемые им ошибки должны обрабатываться в первую очередь. А тип Throwable представляет
наиболее общий тип, так как ему соответствуют все возможные ошибки и исключения, поэтому блоки catch с таким типом должны идти в конце.
В данном случае опять же в блоке try происходит ошибка деления на ноль. Но этой ошибке соответствуют все четыре блока catch
. Для обработки PHP будет
выбирать первый попавшийся, который соответствует типу ошибки. В данном случае это блок для обработки ошибки типа DivisionByZeroError.
Если нам надо обрабатывать в принципе все ошибки и исключения, то мы можем определить только обработку общего для всех них типа Throwable:
try { $result = 5 / 0; echo $result; } catch(Throwable $ex) { echo "Ошибка при выполнении программы"; }
Начиная с версии PHP 8.0 в блоке catch можно просто указать тип обрабатываемого исключения, не определяя переменную:
catch(DivisionByZeroError) { echo "Произошло исключение: деление на ноль"; }
Получение информации об ошибках и исключениях
Интерфейс Throwable
предоставляет ряд методов, которые позволяют получить некоторую информацию о возникшем исключении:
-
getMessage(): возвращает сообщение об ошибке
-
getCode(): возвращает код исключения
-
getFile(): возвращает название файла, в котором возникла ошибка
-
getLine(): возвращает номер строки, в которой возникла ошибка
-
getTrace(): возвращает трассировку стека
-
getTraceAsString(): возвращает трассировку стека в виде строки
Применим некоторые из этих методов:
try { $result = 5 / 0; echo $result; } catch(DivisionByZeroError $ex) { echo "Сообщение об ошибке: " . $ex->getMessage() . "<br>"; echo "Файл: " . $ex->getFile() . "<br>"; echo "Номер строки: " . $ex->getLine() . "<br>"; }
Результат работы:
Сообщение об ошибке: Division by zero Файл: D:\localhost\hello.php Номер строки: 11
Блок finally
Конструкция try..catch
также может определять блок finally. Этот блок выполняется в конце — после блока try и catch
вне зависимости, возникла или нет ошибка. Нередко блок finally
используется для закрытия ресурсов, которые применяются в блоке try.
try { $result = 5 / 0; echo $result . "<br>"; } catch(Throwable $ex) { echo "Ошибка при выполнении программы<br>"; } finally { echo "Блок finally<br>"; } echo "Конец работы программы";
Вывод программы:
Ошибка при выполнении программы Блок finally Конец работы программы
Конструкция try..catch..finally
может содержать либо все три блока, либо только два блока try
и либо блок catch
,
либо блок finally
.
PHP is the language used to build websites on the internet for over ten years. Although, there are a lot of people who think that it’s time to move into something else, PHP is a dynamic programming language, which means that it can be adapted to the current needs. And the PHP Core team has been excellent in bringing out new features that make PHP an attractive language in this time and age.
The flexibility in the PHP language makes it easy to handle things like exceptions in code, which are the out of the ordinary scenarios that can occur. They can be caused by some unexpected input, a bug, or some other problem. PHP 8 is a new version of this language that was released on 26 November 2020. The new version has been adapted to be more secure and handle exceptions better than the previous versions.
Potential exceptions/errors are enclosed inside a try block if exception is encountered, will be thrown to catch or finally block. PHP usually handles exceptions in a separate catch block for each different type of exception.
In this post, you can gain knowledge about what exactly is exception handling, and how it works.
Below are the topics that shall be covered in this blog:
- When, Where, and How to use Exceptions and Errors in PHP?
-
Error Class
-
Exception Class
-
Custom Exception
-
Multiple Exception
-
Global Exception Handler
-
Non-Capturing Catches
#1 When, Where, and How to use Exceptions and Errors in PHP?
PHP 7 introduced the new Throwable interface to unite the exception branches Exception and Error. The entire PHP exception hierarchy is as follows:
interface Throwable
|- Error implements Throwable
|- CompileError extends Error
|- ParseError extends CompileError
|- TypeError extends Error
|- ArgumentCountError extends TypeError
|- ArithmeticError extends Error
|- DivisionByZeroError extends ArithmeticError
|- AssertionError extends Error
|- Exception implements Throwable
|- ClosedGeneratorException
|- DOMException
|- ErrorException
|- IntlException
|- LogicException
|- BadFunctionCallException
|- BadMethodCallException
|- DomainException
|- InvalidArgumentException
|- LengthException
|- OutOfRangeException
|- PharExceptionaddition
|- ReflectionException
|- RuntimeException
|- mysqli_sql_exception
|- OutOfBoundsException
|- OverflowException
|- PDOException
|- RangeException
|- UnderflowException
|- UnexpectedValueException
|- Custom Exception
To catch both exceptions and errors in PHP 8, add a catch block for Exception after catching Throwable
first.
try
{
// Code that may throw an Exception or Error.
} catch (Throwable $t)
{
// Executed only in PHP 7 and more
}
#2 Error Class
Error Class is the base class for all internal PHP errors. Errors can be caught in try/catch block as explained above. Few errors will throw a specific subclass of Error such as Parse Error, Type Error, and so on.
Here are the list of various types of errors (we have covered only the most common ones):
- Parse/Syntax Error
-
Type Error
- Arithmetic Error
-
Assertion Error
-
Value Error
a. Parse/Syntax Error
A syntax/parse error in the code while compilation, a Parse error is thrown. If a code contains an error, the PHP parser cannot interpret the code and it stops working.
Let’s look into a simple example for understanding Parse error.
Code:
<?php
$x = "Exception";
y = "Handling";
echo $x . ' ' . y;
?>
Output:
syntax error, unexpected '=' in line 3
b. Type Error
When data type mismatch happens in PHP while doing an operation, a Type error is thrown. There are three scenarios where this type of error is thrown:
- Invalid number of arguments passed to a built-in function.
- Value returned from a function doesn’t match the declared function return type.
- Argument type passed to a function doesn’t match the declared parameter type.
Let’s look into a simple example for understanding Type error.
Code:
<?php
function add(int $x, int $y)
{
return $x + $y;
}
try {
$value = add('Type', 10);
}
catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
?>
Output:
Argument 1 passed to add() must be of the type integer, string given.
c. Arithmetic Error
Occurrence of error while performing a mathematical operation, bit shifting by a negative number or calling an intdiv() function, the Arithmetic error is thrown.
Example With Division Operator:
<?php
try {
intdiv(PHP_INT_MIN, -1);
}
catch (ArithmeticError $e) {
echo $e->getMessage();
}
?>
Output:
Division of PHP_INT_MIN by -1 is not an integer
Example With Modulo Operator:
<?php
try {
$x = 4;
$y = 0;
$result = $x%$y;
}
catch (DivisionByZeroError $e) {
echo $e->getMessage();
}
?>
Output:
Modulo by zero error
Example With Division Operator Which Returns INF:
<?php
try {
$x = 4;
$y = 0;
$result = $x / $y;
}
catch (DivisionByZeroError $e) {
echo $e->getMessage();
}
?>
Output:
INF
Explanation:
There is a very minute difference in the above two examples. The first one contains the Modulo operator
and the second one has the Division operator
. If any variable divided by zero will return an error, Division by zero error
. When any variable divided by zero with modulo operator returns Modulo by zero error
and the variable divided by zero with the division operator also returns anyone the following- INF, NAN, -INF
d. Assertion Error
When an assert() call fails or let’s say when the condition inside the assert() call doesn’t meet, the Assertion error is thrown. String description emits E_DEPRECATED message from PHP 7.2 version. The Assertion Error thrown by assert() will be sent to catch block only if assert.exception=on is enabled in php.ini.
Let’s look into a simple example for understanding assertion error.
Code:
<?php
try {
$x = 1;
$y = 2;
$result = assert($x === $y);
if (!$result) {
throw new DivisionByZeroError('Assertion error');
}
}
catch (AssertionError $e) {
echo $e->getMessage();
}
?>
Output:
Assertion error
e. Value Error
When the type of the argument is correct and the value of it is incorrect, a value error is thrown. These type of errors occurs when:
- Passing a negative value when the function expects a positive value.
- Passing an empty string or array when function expects a non-empty string/array.
Let’s look into a simple examples for understanding value error.
Code:
<?php
$x = strpos("u", "austin", 24);
var_dump($x);
?>
Output:
[Mon Feb 22 20:59:04 2021]
PHP Warning: strpos(): Offset not contained in string in /home/ubuntu/value_error.php on line 2
Code:
<?php
$x = array_rand(array(), 0);
var_dump($x);
?>
[Mon Feb 22 21:04:14 2021] PHP Warning: array_rand(): Array is empty in /home/ubuntu/index.php on line 2
#3 Exception Class
Exception Class occurs when a specified/exceptional error condition changes the normal flow of the code execution.
Exception handling comprises five components i.e, try block, exception, throw, catch block, and finally block.
Let’s look into a simple example for understanding the above-mentioned components
Code:
<?php
function add($x,$y) {
if (is_numeric($x) == False) {
throw new Exception('Num1 is not a number');
}
if (is_numeric($y) == False) {
throw new RuntimeException('Num2 is not a number');
}
return $x + $y;
}
try {
echo add(5,10). "\n";
echo add(5,k). "\n";
}
catch (Exception $e) {
echo 'Exception caught: ', $e->getMessage(), "\n";
}
finally {
echo "Finally Block.\n";
}
// Continue execution
echo "Hello World\n";
?>
Output:
15
Exception caught: Num2 is not a number
Finally Block.
Hello World
Explanation:
Our example is about adding two numbers and we assumed that we might get non-numeric value as input which would raise an error.
-
We created a function called addition with Exception for non-numeric values and If encountered with the exception, will throw it with the exception message.
-
We called the addition function inside a Try block so that non-numeric value error won’t affect/stop the whole execution. All potential exceptions should be enclosed inside a try block.
-
The Catch block will receive any exceptions thrown from the try block and execute the code inside the block. In our case will print the error message ‘Caught exception: Num2 is not a number’.
-
The Finally block will be executed irrespective of whether we received exception or not.
#4 Custom Exception
We use custom exception to make it clear what is being caught in the catch block and to understand the exception in a better way. The custom exception class inherits properties from the PHP exception’s class where you can add your custom functions too. To easily understand the exceptions we can use custom exceptions and can log it for the future use.
If you just want to capture a message, you can do it at follows:
try {
throw new Exception("This is an error message");
}
catch(Exception $e) {
print $e->getMessage();
}
If you want to capture specific error messages which could be easy to understand you can use:
try {
throw new MyException("Error message");
}
catch(MyException $e) {
print "Exception caught: ".$e->getMessage();
}
catch(Exception $e) {
print "Error: ".$e->getMessage();
}
Code:
<?php
class customStringException extends Exception
{
public function myerrorMessage()
{
//error message
$errorMsg = 'Error on line ' . $this->getLine() . ': <b>' . $this->getMessage() . '</b> is not a String';
return $errorMsg;
}
}
class customNumericException extends Exception
{
public function myerrorMessage()
{
//error message
$errorMsg = 'Error on line ' . $this->getLine() . ': <b>' . $this->getMessage() . '</b> is not an Integer';
return $errorMsg;
}
}
function typeCheck($name, $age)
{
if (!is_string($name)) {
throw new customStringException($name);
}
if (!is_numeric($age)) {
throw new customNumericException($age);
} else {
echo $name . " is of age " . $age;
}
}
try {
echo typeCheck("Sara", 25) . "\n";
echo typeCheck(5, 10) . "\n";
}
catch (customStringException $e) {
echo $e->myerrorMessage();
}
catch (customNumericException $e) {
echo $e->myerrorMessage();
}
?>
Output:
Sara is of age 25
Error on line 21: 5 is not a String
Explanation:
The above example is on a type check, we have two variables name and age . Let’s assume $name is of type string and $age is of type integer and we assumed that we might get any type of value as input which would raise an error.
-
We created a function called typeCheck to check the type of the variable with exception. If the condition fails it will throw an exception with an Exception message that we have customized.
-
We created a class
customStringException
to create a custom exception handler with a function callederrorMessage
which would be called when an exception occurs. -
Here we called the typeCheck function inside a try block so that if any error is encountered it could be caught in the catch block.
#5 Multiple Exception
You can also handle multiple exception in a single catch block using the pipe ‘|’ symbol like this:
try {
$error = "Foo / Bar / Baz Exception"; throw new MyBazException($error);
}
catch(MyFooException | MyBarException | MyBazException $e) {
//Do something here
}
#6 Global Exception Handler
In Global Exception Handler, it sets the default exception handler if an exception is not caught within a try/catch block. If no other block is invoked the set_exception_handler function can set a function which will be called in the place of catch. Execution will stop after the exception_handler is called.
set_exception_handler Syntax:
set_exception_handler ( callable
$exception_handler
) : callable
<?php
function exception_handler($exception)
{
echo "Uncaught exception: ", $exception->getMessage(), "\n";
}
set_exception_handler('exception_handler');
throw new Exception('Uncaught Exception');
echo "Not Executed\n";
?>
#7 Non-Capturing Catches
Before PHP version 8, if you wanna catch an exception you will need to store it in a variable irrespective of its usage. You usually must specify the type whenever you use a catch exception. With this Non-Capturing Catch exception, you can ignore the variable.
Example:
try {
// Something goes wrong
}
catch (MyException $exception) {
Log::error("Something went wrong");
}
You can put it this way in PHP 8:
try {
// Something goes wrong
}
catch (MyException) {
Log::error("Something went wrong");
}
Summary:
Here we have explained the basic usage of exceptions and how to implement it in detail.You can quickly track the errors and fix the exceptions that have been thrown in your code. I hope this blog might be useful for you to learn what exception is and the correct usage of it.
If you would like to monitor your PHP code, you can try Atatus here.
Summary: in this tutorial, you will learn how to use the PHP try...catch
statement to handle exceptions.
Introduction to the PHP try…catch statement
In programming, unexpected errors are called exceptions. Exceptions can be attempting to read a file that doesn’t exist or connecting to the database server that is currently down.
Instead of halting the script, you can handle the exceptions gracefully. This is known exception handling.
To handle the exceptions, you use the try...catch
statement. Here’s a typical syntax of the try...catch
statement:
<?php
try {
// perform some task
} catch (Exception $ex) {
// jump to this part
// if an exception occurred
}
Code language: HTML, XML (xml)
In this syntax, the try...catch
statement has two blocks: try
and catch
.
In the try
block, you do some tasks e.g.,reading a file. If an exception occurs, the execution jumps to the catch
block.
In the catch
block, you specify the exception name and the code to handle a specific exception.
PHP try…catch example
The following example shows how to read data from a CSV file:
<?php
$data = [];
$f = fopen('data.csv', 'r');
do {
$row = fgetcsv($f);
$data[] = $row;
} while ($row);
fclose($f);
Code language: HTML, XML (xml)
If the data.csv
file doesn’t exist, you’ll get many warrnings. The following shows the first warning:
PHP Warning: fopen(data.csv): failed to open stream: No such file or directory in ... on line 5
Code language: plaintext (plaintext)
To fix this, you may add an if
statement in every step:
<?php
$data = [];
$f = fopen('data1.csv', 'r');
if (!$f) {
echo 'The file is not accessible.';
exit;
}
do {
$row = fgetcsv($f);
if ($row === null) {
echo 'The stream is invalid.';
exit;
}
if ($row === false) {
echo 'Other errors occurred.';
exit;
}
$data[] = $row;
} while ($row);
// close the file
if (!$f) {
fclose($f);
}
print_r($data);
Code language: HTML, XML (xml)
However, this code mixes the program logic and error handlers.
The advantage of the try...catch
statement is to separate the program logic from the error handlers. Therefore, it makes code easier to follow.
The following illustrates how to use the try...catch
block for reading data from a CSV file:
<?php
$data = [];
try {
$f = fopen('data.csv', 'r');
do {
$row = fgetcsv($f);
$data[] = $row;
} while ($row);
fclose($f);
} catch (Exception $ex) {
echo $ex->getMessage();
}
Code language: HTML, XML (xml)
In this example, if any error occurs in the try...block
, the execution jumps to the catch
block.
The exception variable $ex
is an instance of the Exception
class that contains the detailed information of the error. In this example, we get the detailed error message by calling the getMessage()
method of the $ex
object.
Multiple catch blocks
A try...catch
statement can have multiple catch
blocks. Each catch
block will handle a specific exception:
<?php
try {
//code...
} catch (Exception1 $ex1) {
// handle exception 1
} catch (Exception2 $ex2) {
// handle exception 2
} catch (Exception1 $ex3) {
// handle exception 3
}
...
Code language: HTML, XML (xml)
When a try...catch
statement has multiple catch
blocks, the order of exception should be from the specific to generic. And the last catch
block should contain the code for handling the most generic exception. By doing this, the try...catch
statement can catch all the exceptions.
If you have the same code that handles multiple types of exceptions, you can place multiple exceptions in one catch
block and separate them by the pipe (|
) character like this:
<?php
try {
//code...
} catch (Exception1 | Exception2 $ex12) {
// handle exception 1 & 2
} catch (Exception3 $ex3) {
// handle exception 3
}
Code language: HTML, XML (xml)
By specifying multiple exceptions in the catch
block, you can avoid code duplication. This feature has been supported since PHP 7.1.0.
Ignoring the exception variable
As of PHP 8.0, the variable name for the caught exception is optional like this:
<?php
try {
//code...
} catch (Exception) {
// handle exception
}
Code language: HTML, XML (xml)
In this case, the catch
block will still execute but won’t have access the Exception
object.
Summary
- Use the
try...catch
statement to handle exceptions. - The
try...catch
statement separates the program logic and exception handlers. - Use multiple catch blocks to handle multiple exceptions. Place the most specific exception first and the least specific exception after.
- Specify a list of pipe-separated exceptions in a single
catch
block if the same code can handle multiple exceptions. - Ignore the exception variable when you don’t want to access the detail of the exception.
Did you find this tutorial useful?