Table of Contents
- Extending Exceptions
PHP has an exception model similar to that of other programming
languages. An exception can be thrown, and caught («catched») within
PHP. Code may be surrounded in a try block, to facilitate the catching
of potential exceptions. Each try must have at least one corresponding
catch or finally block.
If an exception is thrown and its current function scope has no catch
block, the exception will «bubble up» the call stack to the calling
function until it finds a matching catch block. All finally blocks it encounters
along the way will be executed. If the call stack is unwound all the way to the
global scope without encountering a matching catch block, the program will
terminate with a fatal error unless a global exception handler has been set.
The thrown object must be an instanceof Throwable.
Trying to throw an object that is not will result in a PHP Fatal Error.
As of PHP 8.0.0, the throw keyword is an expression and may be used in any expression
context. In prior versions it was a statement and was required to be on its own line.
catch
A catch block defines how to respond to a thrown exception. A catch
block defines one or more types of exception or error it can handle, and
optionally a variable to which to assign the exception. (The variable was
required prior to PHP 8.0.0.) The first catch block a thrown exception
or error encounters that matches the type of the thrown object will handle
the object.
Multiple catch blocks can be used to catch different classes of
exceptions. Normal execution (when no exception is thrown within the try
block) will continue after that last catch block defined in sequence.
Exceptions can be thrown (or re-thrown) within a catch block. If not,
execution will continue after the catch block that was triggered.
When an exception is thrown, code following the statement will not be
executed, and PHP will attempt to find the first matching catch block.
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().
As of PHP 7.1.0, a catch block may specify multiple exceptions
using the pipe (|) character. This is useful for when
different exceptions from different class hierarchies are handled the
same.
As of PHP 8.0.0, the variable name for a caught exception is optional.
If not specified, the catch block will still execute but will not
have access to the thrown object.
finally
A finally block may also be specified after or
instead of catch blocks. Code within the finally block will always be
executed after the try and catch blocks, regardless of whether an
exception has been thrown, and before normal execution resumes.
One notable interaction is between the finally block and a return statement.
If a return statement is encountered inside either the try or the catch blocks,
the finally block will still be executed. Moreover, the return statement is
evaluated when encountered, but the result will be returned after the finally block
is executed. Additionally, if the finally block also contains a return statement,
the value from the finally block is returned.
Global exception handler
If an exception is allowed to bubble up to the global scope, it may be caught
by a global exception handler if set. The set_exception_handler()
function can set a function that will be called in place of a catch block if no
other block is invoked. The effect is essentially the same as if the entire program
were wrapped in a try—catch block with that function as the catch.
Notes
Note:
Internal PHP functions mainly use
Error reporting, only modern
Object-oriented
extensions use exceptions. However, errors can be easily translated to
exceptions with ErrorException.
This technique only works with non-fatal errors, however.Example #1 Converting error reporting to exceptions
<?php
function exceptions_error_handler($severity, $message, $filename, $lineno) {
throw new ErrorException($message, 0, $severity, $filename, $lineno);
}set_error_handler('exceptions_error_handler');
?>
Examples
Example #2 Throwing an Exception
<?php
function inverse($x) {
if (!$x) {
throw new Exception('Division by zero.');
}
return 1/$x;
}
try {
echo
inverse(5) . "\n";
echo inverse(0) . "\n";
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
}// Continue execution
echo "Hello World\n";
?>
The above example will output:
0.2 Caught exception: Division by zero. Hello World
Example #3 Exception handling with a finally block
<?php
function inverse($x) {
if (!$x) {
throw new Exception('Division by zero.');
}
return 1/$x;
}
try {
echo
inverse(5) . "\n";
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
} finally {
echo "First finally.\n";
}
try {
echo
inverse(0) . "\n";
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
} finally {
echo "Second finally.\n";
}// Continue execution
echo "Hello World\n";
?>
The above example will output:
0.2 First finally. Caught exception: Division by zero. Second finally. Hello World
Example #4 Interaction between the finally block and return
<?phpfunction test() {
try {
throw new Exception('foo');
} catch (Exception $e) {
return 'catch';
} finally {
return 'finally';
}
}
echo
test();
?>
The above example will output:
Example #5 Nested Exception
<?phpclass MyException extends Exception { }
class
Test {
public function testing() {
try {
try {
throw new MyException('foo!');
} catch (MyException $e) {
// rethrow it
throw $e;
}
} catch (Exception $e) {
var_dump($e->getMessage());
}
}
}$foo = new Test;
$foo->testing();?>
The above example will output:
Example #6 Multi catch exception handling
<?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();?>
The above example will output:
Example #7 Omitting the caught variable
Only permitted in PHP 8.0.0 and later.
<?phpclass SpecificException extends Exception {}
function
test() {
throw new SpecificException('Oopsie');
}
try {
test();
} catch (SpecificException) {
print "A SpecificException was thrown, but we don't care about the details.";
}
?>
Example #8 Throw as an expression
Only permitted in PHP 8.0.0 and later.
<?phpfunction test() {
do_something_risky() or throw new Exception('It did not work');
}
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 ¶
13 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"
Генерация исключений
Последнее обновление: 24.03.2021
PHP по умолчанию представляет ситуации, в которых автоматически генерируются ошибки и исключения, например, при делении на ноль. Но иногда возникает
необходимость самим вручную сгенерировать исключение. Например:
class Person
{
private $name, $age;
function __construct($name, $age)
{
$this->name = $name;
$this->age = $age;
}
function printInfo()
{
echo "Name: $this->name<br>Age: $this->age";
}
}
$tom = new Person("Tom", -105);
$tom->printInfo();
Здесь класс Person через конструктор получает имя и возраст пользователя. Однако в реальности мы можем передать любые значения, например, отрицательный возраст.
Понятно, что это недействительное значение. Чтобы избежать подобной ситуации одним из решений является генерация исключения при получении невалидного значения.
Для генерации исключения применяется оператор throw, после которого указывается объект исключения.
Например, изменим выше определенный код класса Person:
class Person
{
private $name, $age;
function __construct($name, $age)
{
if($age < 0)
{
throw new Exception("Недействительный возраст");
}
$this->name = $name;
$this->age = $age;
}
function printInfo()
{
echo "Name: $this->name<br>Age: $this->age";
}
}
$tom = new Person("Tom", -105);
$tom->printInfo();
Теперь если возраст отрицательный, то будет генерироваться исключение типа Exception.
throw new Exception("Недействительный возраст");
В качестве параметра конструктор класса Exception получает сообщение об ошибке, которое будет выводиться при возникновении исключения.
В итоге при выполнении строки
$tom = new Person("Tom", -105);
будет выполняться код
throw new Exception("Недействительный возраст");
И в итоге в браузере мы увидем информацию об ошибке:
Fatal error: Uncaught Exception: Недействительный возраст in D:\localhost\hello.php:17 Stack trace: #0 D:\localhost\hello.php(26): Person->__construct('Tom', -105) #1 {main} thrown in D:\localhost\hello.php on line 17
Поскольку вызов конструктора класса Person создает ситуацию, в которой потенциально может возникнуть исключение, то лучше вызов конструктора поместить
в конструкцию try..catch:
class Person
{
private $name, $age;
function __construct($name, $age)
{
$this->name = $name;
if($age < 0)
{
throw new Exception("Недействительный возраст");
}
$this->age = $age;
}
function printInfo()
{
echo "Name: $this->name<br>Age: $this->age";
}
}
try
{
$tom = new Person("Tom", -105);
$tom->printInfo();
}
catch(Exception $ex)
{
echo $ex -> getMessage();
}
Вывод браузера:
Недействительный возраст
Создание классов исключений
При генерации исключений мы можем полагаться на встроенные классы исключений, как в примере с классом Exception выше. Однако
может возникнуть необходимость передать несколько больше информации при генерации или как-то по своему настроить поведение класса исключения. В
этом случае мы можем создать свой класс исключения, заточенный под конкретные нужды:
class PersonInvalidAgeException extends Exception
{
function __construct($age)
{
$this -> message = "Недействительный возраст: $age. Возраст должен быть в диапазоне от 0 до 120";
}
}
class Person
{
private $name, $age;
function __construct($name, $age)
{
$this->name = $name;
if($age < 0)
{
throw new PersonInvalidAgeException($age);
}
$this->age = $age;
}
function printInfo()
{
echo "Name: $this->name<br>Age: $this->age";
}
}
try
{
$tom = new Person("Tom", -105);
$tom->printInfo();
}
catch(PersonInvalidAgeException $ex)
{
echo $ex -> getMessage();
}
Для примера здесь определен простенькй класс исключения, который унаследован от класса Exception. (В реальности чтобы создать класс исключения,
достаточно реализовать интерфейс Throwable). В этом классе переопределяется конструктор, который в качестве параметра принимает недействительный возраст.
Выводимое значение в классе Exception хранится в свойстве message, соответственно в классе-наследнике PersonInvalidAgeException
мы можем использовать это свойство для установки своего сообщения. В итоге при генерации исключения браузер выведет соответствующее сообщение об
ошибке:
Недействительный возраст: -105. Возраст должен быть в диапазоне от 0 до 120
Содержание
- Наследование исключений
Модель исключений (exceptions) в PHP 5 схожа с используемыми в других языках программирования.
Исключение можно сгенерировать (как говорят, «выбросить») при помощи оператора
throw, и можно перехватить (или, как говорят, «поймать»)
оператором catch. Код генерирующий исключение, должен
быть окружен блоком try, для того чтобы можно было
перехватить исключение. Каждый блок try
должен иметь как минимум один соответствующий ему блок catch или finally.
Генерируемый объект должен принадлежать классу Exception
или наследоваться от Exception. Попытка сгенерировать
исключение другого класса приведет к неисправимой ошибке.
catch
Можно использовать несколько блоков catch,
перехватывающих различные классы исключений.
Нормальное выполнение (когда не генерируются исключения в блоках
try или когда класс сгенерированного исключения не
совпадает с классами, объявленными в соответствующих блоках
catch) будет продолжено за последним блоком
catch. Исключения так же могут быть сгенерированы (или
вызваны еще раз) оператором throw
внутри блока catch.
При генерации исключения код следующий после описываемого выражения
исполнен не будет, а PHP предпримет попытку найти
первый блок catch, перехватывающий исключение данного
класса. Если исключение не будет перехвачено, PHP выдаст сообщение об
ошибке: «Uncaught Exception …» (Неперехваченное
исключение), если не был определен обработчик ошибок при помощи
функции set_exception_handler().
finally
В PHP 5.5 и более поздних версиях также можно использовать блок finally
после или вместо блока catch. Код в блоке
finally всегда будет выполняться после кода в блоках
try и catch, вне зависимости было ли
брошено исключение или нет, перед тем как продолжится нормальное выполнение кода.
whether an exception has been thrown, and before normal execution resumes.
Примеры
Пример #3 Выброс исключений
<?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 "Hello World\n";
?>
Результат выполнения данного примера:
0.2 Выброшено исключение: Деление на ноль. Hello World
Пример #4 Вложенные исключения
<?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 "Hello World\n";
?>
Результат выполнения данного примера:
0.2 Первое finally. Поймано исключение: Деление на ноль. Второе finally. Hello World
Пример #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();?>
Результат выполнения данного примера:
Вернуться к: Справочник языка
Если ты изучаешь ООП, то наверняка натыкался на понятие исключения, а может даже уже видел его где-то в коде. В этом уроке я постараюсь объяснить, что такое исключение в PHP и зачем они нужны. Расскажу когда, какое и как правильно применять то или иное исключение при разработке.
Что такое исключение в PHP
Исключения — это специальное условие, которое возникает в исключительной ситуации (обычно в случае ошибки), при возникновении которого мы можем понять, что что-то в процессе отличается от предполагаемого хода событий.
Пример: Предположим, мы разрабатываем блог и работаем над методами удаления категории. По логике вещей нельзя удалить категорию, в которой есть посты. Здесь нам приходят на помощь исключения. Очень урезанный и простой пример, но отражающий суть:
// Где-то (модель или сервис)
public function delete($id)
{
$category = Category::find($id);
// Если категория не найдена - кидаем исключение
if (!$category) throw new Exception('Page Not Found!');
// Если в категории есть посты - кидаем исключение
if (count($category->posts) > 0) throw new Exception('Cannot delete category with posts!');
// Если всё хорошо - продолжаем выполнение кода
// Удаляем категорию
}
// В контроллере
public function deleteAction($id)
{
try {
// Если метод delete() из модели возвращает true
$model->delete($id);
} catch (Exception $e) {
// Если false - ловим брошенное из модели исключение
echo $e->getMessage();
// Или вывести в уведомление через сессию, например
// Session::set('error', $e->getMessage());
}
}
Согласитесь, удобно. Вместо того, чтобы просто возвращать false в случае, когда срабатывает условие if (count($category->products) > 0), лучше кинуть исключение и как-то оповестить пользователя о каких-либо возникших исключительных ситуациях. Если же мы просто вернём false, то мы сами со временем не сможем понять, что именно там случилось и почему этот метод не работает. Поэтому, я советую всегда пользоваться исключениями, но слишком не увлекаясь этим делом.
Класс Exception
Исключение (Exception) – это объект, являющийся экземпляром встроенного класса Exception. Этот объект создаётся для хранения информации о произошедшей ошибке и для вывода сообщений о ней.
Конструктор класса Exception может принимать два необязательных параметра — это строка, содержащая сообщение об ошибке и её код. Класс Exception так же содержит методы, помогающие установить причину возникшей ошибки:
getMessage()– возвращает строку, которая была передана конструктору и содержит сообщение об ошибке.getCode()– возвращает код ошибки (тип int), который был передан конструктору.getFile()– возвращает имя файла в котором было сгенерировано исключение.getLine()– возвращает номер строки в которой было сгенерировано исключение.getTrace()– возвращает многомерный массив, содержащий последовательность методов, вызов которых привёл к генерированию исключения. Так же содержит параметры, которые были переданы этим методам и номера строк, где осуществлялись вызовы.getTraceAsString()– возвращает строковую версию данных, которые возвращает методgetTrace().__toString()– магический метод, который вызывается, когда экземпляр классаExceptionприводится к строке.
Генерация исключений
Для генерации исключения используется ключевое слово throw и экземпляр класса Exception. С английского throw переводится как «бросать», что очень точно описывает поведение этого оператора. Он генерирует (бросает) исключение в каком-либо методе (в котором может случиться нестандартная, исключительная ситуация) и останавливает дальнейшее выполнение кода, тем самым предоставляя возможность обработать это исключение в методе (в любом месте приложения), который будет вызывать данный метод с брошенным исключением.
// Класс User
class User
{
private $name;
public function setName($name)
{
if (!$name) throw new InvalidArgumentException('Имя не задано!');
if (strlen($name) < 3) throw new LengthException('Имя должно быть больше 3-х символов!');
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
// ...
}
Обработка исключений
И так, метод, который вызывает метод, в котором в свою очередь может быть брошено исключение, должен сам его обрабатывать. Обработка исключения производится при помощи операторов try - catch. Блок кода, который может поймать исключение, располагается после try. Блок кода, который обрабатывает исключение, располагается после оператора catch. В переводе с английского try означает «пытаться», что очень точно отражает суть этого оператора, ведь мы пытаемся выполнить блок кода после него, а если не получается то выполняется блок кода после catch. Catch переводится как «ловить». Он фактически «ловит» сгенерированное исключение. В примере ниже мы ловим исключение из метода setName() класса User из примера выше:
// Где-то ловим исключение и обрабатываем его
try {
$user = new User();
$user->setName('John');
// Случится исключение InvalidArgumentException
$user->setName('');
// Случится исключение LengthException
$user->setName('Jo');
echo $user->getName();
} catch (Exception $e) {
echo "Message: {$e->getMessage()}<br>
Code: {$e->getCode()}<br>
File: {$e->getFile()}<br>
Line: {$e->getLine()}";
}
Ловим исключение в блоке try:
try {
// ...
$config = "config.php";
if (!file_exists($config)) {
throw new Exception("Configuration file not found.");
}
// ...
} catch (Exception $e) {
echo $e->getMessage();
die();
}
Но так делать не рекомендуется (try/catch и throw на одном уровне). В этом случае проще написать if!
Оператор catch внешне напоминает объявление метода с уточнением типа его аргумента. Когда генерируется исключение, управление передаётся оператору catch, при этом в качестве аргумента ему передаётся объект типа Exception.
Создание подклассов класса Exception
От встроенного класса Exception можно унаследовать классы для своих собственных исключений. Делать это можно для того чтобы расширить его функциональность или создать свой собственный тип ошибок. Создание своих собственных типов ошибок нужно для того, чтобы была возможность по-разному обрабатывать разные исключения. Для этого существует возможность писать несколько операторов catch. Какой именно из них вызовется, будет зависеть от типа сгенерированного исключения, от типа, который уточнён в аргументе и от порядка, в котором расположены операторы catch.
Пример собственного класса исключения:
<?php
/**
* Определим свой класс исключения
*/
class MyException extends Exception
{
// Переопределим исключение так, что параметр message станет обязательным
public function __construct($message, $code = 0, Exception $previous = null) {
// некоторый код
// убедитесь, что все передаваемые параметры верны
parent::__construct($message, $code, $previous);
}
// Переопределим строковое представление объекта.
public function __toString() {
return __CLASS__ . ": [{$this->code}]: {$this->message}\n";
}
public function customFunction() {
echo "Мы можем определять новые методы в наследуемом классе\n";
}
}
Полная иерархия исключений в PHP
Throwable (интерфейс)
├── Exception (реализует Throwable)
│ ├── LogicException (расширяет Exception)
│ │ ├── BadFunctionCallException (расширяет LogicException)
│ │ │ └── BadMethodCallException (расширяет BadFunctionCallException)
│ │ ├── DomainException (расширяет LogicException)
│ │ ├── InvalidArgumentException (расширяет LogicException)
│ │ ├── LengthException (расширяет LogicException)
│ │ └── OutOfRangeException (расширяет LogicException)
│ └── RuntimeException (расширяет Exception)
│ ├── OutOfBoundsException (расширяет RuntimeException)
│ ├── OverflowException (расширяет RuntimeException)
│ ├── RangeException (расширяет RuntimeException)
│ ├── UnderflowException (расширяет RuntimeException)
│ └── UnexpectedValueException (расширяет RuntimeException)
└── Error (реализует Throwable)
├── AssertionError (расширяет Error)
├── ParseError (расширяет Error)
└── TypeError (расширяет Error)
Throwable
Throwable — это даже не исключение, а интерфейс, который реализуют все остальные рассматриваемые классы. Добавлен в PHP7.
Exception
Базовый класс для исключений. Стандартная библиотека SPL вводит две группы исключений, два надкласса: для исключений в логике: LogicException и исключений времени исполнении RuntimeException.
LogicException
Используется, когда ваш код возвращает значение, которое не должен возвращать. Часто вызывается при разных багах в коде. Потомки этого класса используются в более специализированных ситуациях. Если ни одна из них не подходит под ваш случай, можно использовать LogicException.
BadFunctionCallException
Используется, когда вызываемой функции физически не существует или когда в вызове используется неверное число аргументов. Редко бывает нужно.
BadMethodCallException
Подкласс BadFunctionCallException. Аналогично ему используется для методов, которые не существуют или которым передано неверное число параметров. Всегда используйте внутри __call(), в основном для этого оно и применяется.
Пример использования этих двух исключений:
// Для метода в __call
class Foo
{
public function __call($method, $args)
{
switch ($method) {
case 'someExistentClass': /* do something positive... */ break;
default:
throw new BadMethodCallException('Метод ' . $method . ' не может быть вызван');
}
}
}
// процедурный подход function
foo($arg1, $arg2)
{
$func = 'do' . $arg2;
if (!is_callable($func)) {
throw new BadFunctionCallException('Функция ' . $func . ' не может быть вызвана');
}
}
DomainException
Если в коде подразумеваются некие ограничения для значений, то это исключение можно вызывать, когда значение выходит за эти ограничения. Например, у вас дни недели обозначаются числами от 1 до 7, а ваш метод получает внезапно на вход 0 или 9, или, скажем, вы ожидаете число, обозначающее количество зрителей в зале, а получаете отрицательное значени. В таких случаях и вызывается DomainException. Также можно использовать для разных проверок параметров, когда параметры нужных типов, но при этом не проходят проверку на значение. Например:
if ($a > 5)
throw new DomainException ("Переменная a должна быть меньше 5");
InvalidArgumentException
Вызываем, когда ожидаемые аргументы в функции/методе некорректно сформированы. Например, ожидается целое число, а на входе строка или ожидается GET, а пришел POST и т.п.
public function foo($number) {
if(!is_numeric($number)) {
throw new InvalidArgumentException('На входе ожидалось целое число!');
}
}
LengthException
Вызываем, если длина чего-то слишком велика или мала. Например, имя файла слишком короткое или длина строки слишком большая.
RuntimeException
Исключения времени выполнения нужно вызывать, когда код самостоятельно не может справиться с некой ситуацией во время своего выполнения. Подклассы этого класса сужают область применения, но, если ни один из них не подходит для вашей ситуации, смело пользуйтесь этим классом. Вот из каких пяти подклассов вам можно выбирать:
OutOfBoundsException
Вызываем, когда обнаружили попытку использования неправильного ключа, например, в ассоциативном массиве или при реализации ArrayAccess. Используется тогда, когда ошибка не может быть обнаружена до прогона кода. То есть, например, какие именно ключи будут легитимными, определяется динамически уже во время выполнения.
Пример использования в реализации ArrayAccess:
public function offsetGet($offset) {
if(!isset($this->objects[$offset])) {
throw new OutOfBoundsException("Смещение '$offset' вышло из заданного диапазона");
}
return $this->objects[$offset];
}
OutOfRangeException
Используется, когда встречаем некорректный индекс, но на этот раз ошибка должна быть обнаружена ещё до прогона кода, например, если мы пытаемся адресовать элемент массива, который в принципе не поддерживается. То есть если функция, возвращающая день недели по его индексу от 1 до 7, получает внезапно 9, то это DomainException — ошибка логики, а если у нас есть массив с днями недели с индексами от 1 до 7, а мы пытаемся обратиться к элементу с индексом 9, то это уже OutOfRangeException.
OverflowException
Исключение вызываем, когда есть переполнение. Например, имеется некий класс-контейнер, который может принимать только 5 элементов, а мы туда пытаемся записать шестой.
UnderflowException
Обратная OverflowException ситуация, когда, например, класс-контейнер имеет недостаточно элементов для осуществления операции. Например, когда он пуст, а вы пытаетесь удалить элемент.
RangeException
Вызывается, когда значение выходит за границы некоего диапазона. Похоже на DomainException, но используется при возврате из функции, а не при входе. Если мы не можем вернуть легитимное значение, мы выбрасываем это исключение. То есть, к примеру, функция у вас принимает целочисленный индекс и использует другую функцию, чтоб получить некое значение по этой сущности. Та функция вернула null, но ваша функция не имеет права возвращать Null. В таком случае можно применить это исключение. То есть между ними примерно такая же разница, как между OutOfBoundsException и OutOfRangeException.
UnexpectedValueException
Используется, когда значение выходит из ряда ожидаемых значений. Часто применяется, когда то, что вернулось из вызываемой функции, не соответствует тому, что мы от нее ожидаем в ответе по типу или значению. Сюда не относятся арифметические ошибки или ошибки, связанные с буфером.
Важно, что, в отличие от InvalidArgumentException, здесь мы имеем дело, в основном, с возвращаемыми значениями. Часто мы заранее не можем быть уверены в том, что придет в ответе от функции (особенно сторонней). Скажем, мы используем некую стороннюю функцию, использующую API ВКонтакте, и возвращющую количество постов для пользователя. Она всегда возвращала целое неотрицательное число, и вдруг неожиданно возвращает отрицательное число. Это не соответствует документации. Соответственно, чтобы подстраховаться от таких ситуаций, мы можем проверять результат такого вызова и, если он отличается от ожидаемого, выбрасывать UnexpectedValueException.
Вот пример, когда у нас есть список констант, и функция getValueOfX должна гарантированно возвращать значение одной из них.
const TYPE_FOO = 'foo';
const TYPE_BAR = 'bar';
public function doSomething($y) {
$x = ModuleUsingSomeExternalAPI::getValueOfX($y);
if($x != self::TYPE_FOO || $x != self::TYPE_BAR) {
throw new UnexpectedValueException('Параметр должен быть в виде TYPE_* констант');
}
}
Error
Добавлено в PHP7 для обработки фатальных ошибок. То есть многие из ошибок, которые раньше приводили к Fatal Error, сейчас могут обрабатываться в блоках try/catch. Эти ошибки вызываются самим PHP, нет нужды их вызывать, как Exception. Класс Error имеет три подкласса:
AssertionError
Вызывается, когда условие, заданное методом assert(), не выполняется.
ParseError
Для ошибок парсинга, когда подключаемый по include/require код вызывает ошибку синтаксиса, ошибок функции eval() и т.п.
try {
require 'file-with-syntax-error.php';
} catch (ParseError $e) {
// обработка ошибки
}
TypeError
Используется для ошибок несоответствия типов данных. В PHP7 введена опциональная строгая типизация. Вот для поддержки ошибок, связанных с ней, и служит этот класс. Например, если функция ожидает на входе аргумент типа int, а вы ее вызываете со строковым аргументом.
if (!is_string($name)) throw new TypeError('Имя должно быть стройкой!');
Учебный пример, в котором есть примеры использования всех классов исключений:
class Example
{
protected $author;
protected $month;
protected $goals = [];
public function exceptions(int $a, int $b): int
{
$valid_a = [7, 8, 9];
if (!is_int($a)) {
throw new InvalidArgumentException("a должно быть целочисленным!");
}
if ($a > 5 || !in_array($a, $valid_a, true)) {
throw new DomainException("a не может быть больше 5");
}
$c = $this->getByIndex($a);
if (!is_int($c)) {
throw new RangeException("c посчитался неправильно!");
} else {
return $c;
}
}
private function getByIndex($a)
{
return ($a < 100) ? $a + 1 : null;
}
public function deleteNextGoal()
{
if (empty($this->goals)) {
throw new UnderflowException("Нет цели, чтобы удалить!");
} elseif (count($this->goals) > 100000) {
throw new OverflowException("Система не может оперировать больше, чем 100000 целями одновременно!");
} else {
array_pop($this->goals);
}
}
public function getGoalByIndex($i)
{
if (!isset ($this->goals[$i])) {
throw new OutOfBoundsException("Нет цели с индексом $i"); // легитимные значения известны только во время выполнения
} else {
return $this->goals[$i];
}
}
public function setPublicationMonth(int $month)
{
if ($month < 1 || $month > 12) {
throw new OutOfRangeException("Месяц должен быть от 1 до 12!"); // легитимные значения известны заранее
}
$this->month = $month;
}
public function setAuthor($author)
{
if (mb_convert_case($author, MB_CASE_UPPER) !== $author) {
throw new InvalidArgumentException("Все буквы имени автора должны быть заглавными");
} else {
if (mb_strlen($author) > 255) {
throw new LengthException("Поле автор не должно быть больше 255 сиволов!");
} else {
$this->author = $author;
}
}
}
public function __call(string $name, array $args)
{
throw new BadMethodCallException("Метод Example>$name() не существует");
}
}
Вот и всё. Думаю, материал буде полезен как новичкам, так и более продвинутым программистам. Я постарался систематизировать информацию об исключениях в одной статье.
Документация по исключениям в PHP здесь .
Ещё почитать на гитхабе .
Генерация исключений
PHP по умолчанию представляет ситуации, в которых автоматически генерируются ошибки и исключения, например, при делении на ноль. Но иногда возникает необходимость самим вручную сгенерировать исключение:
<?
class Person {
private $name, $age;
function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
}
function printInfo() {
echo "Name: $this->name<br>Age: $this->age";
}
}
$tom = new Person("Tom", -105);
$tom->printInfo();
Здесь класс Person через конструктор получает имя и возраст пользователя. Однако в реальности мы можем передать любые значения, например, отрицательный возраст. Понятно, что это недействительное значение. Чтобы избежать подобной ситуации одним из решений является генерация исключения при получении невалидного значения.
Для генерации исключения применяется оператор throw, после которого указывается объект исключения.
Например, изменим выше определенный код класса Person:
<?
class Person {
private $name, $age;
function __construct($name, $age) {
if($age < 0) {
throw new Exception("Недействительный возраст");
}
$this->name = $name;
$this->age = $age;
}
function printInfo() {
echo "Name: $this->name<br>Age: $this->age";
}
}
$tom = new Person("Tom", -105);
$tom->printInfo();
Теперь если возраст отрицательный, то будет генерироваться исключение типа Exception:
throw new Exception("Недействительный возраст");
В качестве параметра конструктор класса Exception получает сообщение об ошибке, которое будет выводиться при возникновении исключения. В итоге при выполнении строки:
$tom = new Person("Tom", -105);
Будет выполняться код:
throw new Exception("Недействительный возраст");
И в итоге в браузере мы увидем информацию об ошибке:
Fatal error: Uncaught Exception: Недействительный возраст in D:\localhost\hello.php:17 Stack trace: #0
D:\localhost\hello.php(26): Person->__construct('Tom', -105) #1 {main} thrown in D:\localhost\hello.php on line 17
Поскольку вызов конструктора класса Person создает ситуацию, в которой потенциально может возникнуть исключение, то лучше вызов конструктора поместить в конструкцию try..catch:
<?
class Person {
private $name, $age;
function __construct($name, $age) {
$this->name = $name;
if($age < 0) {
throw new Exception("Недействительный возраст");
}
$this->age = $age;
}
function printInfo() {
echo "Name: $this->name<br>Age: $this->age";
}
}
try {
$tom = new Person("Tom", -105);
$tom->printInfo();
}
catch(Exception $ex) {
echo $ex -> getMessage();
}
Вывод браузера:
Недействительный возраст
Создание классов исключений
При генерации исключений мы можем полагаться на встроенные классы исключений, как в примере с классом Exception выше. Однако может возникнуть необходимость передать несколько больше информации при генерации или как-то по своему настроить поведение класса исключения. В этом случае мы можем создать свой класс исключения, заточенный под конкретные нужды:
<?
class PersonInvalidAgeException extends Exception {
function __construct($age) {
$this -> message = "Недействительный возраст: $age. Возраст должен быть в диапазоне от 0 до 120";
}
}
class Person {
private $name, $age;
function __construct($name, $age) {
$this->name = $name;
if($age < 0) {
throw new PersonInvalidAgeException($age);
}
$this->age = $age;
}
function printInfo() {
echo "Name: $this->name<br>Age: $this->age";
}
}
try {
$tom = new Person("Tom", -105);
$tom->printInfo();
}
catch(PersonInvalidAgeException $ex) {
echo $ex -> getMessage();
}
Для примера здесь определен простенькй класс исключения, который унаследован от класса Exception. (В реальности чтобы создать класс исключения, достаточно реализовать интерфейс Throwable). В этом классе переопределяется конструктор, который в качестве параметра принимает недействительный возраст.
Выводимое значение в классе Exception хранится в свойстве message, соответственно в классе-наследнике PersonInvalidAgeException мы можем использовать это свойство для установки своего сообщения. В итоге при генерации исключения браузер выведет соответствующее сообщение об ошибке:
Недействительный возраст: -105. Возраст должен быть в диапазоне от 0 до 120
