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

Исключения

Содержание

  • Наследование исключений

В PHP реализована модель исключений, аналогичная тем, что используются в других языках программирования.
Исключение в PHP может быть выброшено (throw) и поймано (catch).
Код может быть заключён в блок try, чтобы облегчить обработку потенциальных исключений.
У каждого блока try должен быть как минимум один соответствующий блок catch или finally.

Если выброшено исключение, а в текущей области видимости функции нет блока catch,
исключение будет «подниматься» по стеку вызовов к вызывающей функции, пока не найдёт подходящий блок catch.
Все блоки finally, которые встретятся на этом пути, будут выполнены.
Если стек вызовов разворачивается до глобальной области видимости, не встречая подходящего блока catch,
программа завершается с неисправимой ошибкой, если не был установлен глобальный обработчик исключений.

Выброшенный объект должен наследовать (instanceof) интерфейс Throwable.
Попытка выбросить объект, который таковым не является, приведёт к неисправимой ошибке PHP.

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

catch

Блок catch определяет, как реагировать на выброшенное исключение.
Блок catch определяет один или несколько типов исключений или ошибок, которые он может обработать,
и, по желанию, переменную, которой можно присвоить исключение
(указание переменной было обязательно до версии PHP 8.0.0).
Первый блок catch, с которым столкнётся выброшенное исключение или ошибка
и соответствует типу выброшенного объекта, обработает объект.

Несколько блоков catch могут быть использованы для перехвата различных классов исключений.
Нормальное выполнение (когда исключение не выброшено в блоке try)
будет продолжаться после последнего блока catch, определённого в последовательности.
Исключения могут быть выброшены (throw) (или повторно выброшены) внутри блока catch.
В противном случае выполнение будет продолжено после блока catch, который был вызван.

При возникновении исключения, код, следующий за утверждением, не будет выполнен,
а PHP попытается найти первый подходящий блок catch.
Если исключение не поймано, будет выдана неисправимая ошибка PHP
с сообщением «Uncaught Exception ...«,
если только обработчик не был определён с помощью функции set_exception_handler().

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

Начиная с версии PHP 8.0.0, имя переменной для пойманного исключения является необязательным.
Если оно не указано, блок catch будет выполнен,
но не будет иметь доступа к выброшенному объекту.

finally

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

Одно из заметных взаимодействий происходит между блоком finally и оператором return.
Если оператор return встречается внутри блоков try или catch, блок finally
всё равно будет выполнен. Более того, оператор return выполнится, когда встретится,
но результат будет возвращён после выполнения блока finally.
Кроме того, если блок finally также содержит оператор return,
возвращается значение из блока finally.

Глобальный обработчик исключений

Если исключению разрешено распространяться на глобальную область видимости,
оно может быть перехвачено глобальным обработчиком исключений, если он установлен.
Функция set_exception_handler() может задать функцию,
которая будет вызвана вместо блока catch, если не будет вызван никакой другой блок.
Эффект по сути такой же, как если бы вся программа была обёрнута в блок trycatch
с этой функцией в качестве 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"

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

Конструкция 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 и Throwable в PHP

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

Блок catch

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

try
{
	$result = 5 / 0;
	echo $result;
}
catch(ParseError $p)
{
    echo "Произошла ошибка парсинга";
}
catch(DivisionByZeroError $d)
{
	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.

Время на прочтение
6 мин

Количество просмотров 38K

Эта «небольшая» статейка является развитием темы затронутой в этой статье.
Как известно, PHP зародился довольно давно и уже тогда возник вопрос, что делать с возникающими ошибками. Perl, который является несомненным прародителем PHP по умолчанию не имел какой-либо системы обработки ошибок. При возникновении любой ошибки сервер выбрасывал 500-ю ошибку и на этом все заканчивалось. Поэтому Warnings, Fatal Errors и Notices были настоящим прорывом в облегчении и без того нелегкого труда программиста. Однако время шло, механизмы PHP не менялись, а технологии, как известно, на месте стоять не любят.

И вот в PHP 5.0, наконец-то, в арсенале программистов появилось такое мощное средство как исключение или Exception. Достоинств у Exception много, опишу лишь некоторые (возможно, я выражаюсь неточно или даже безграмотно, но мне было просто лень выискивать научные термины для описания преимуществ, потому описаны они «своими словами»):

  • Сквозная генерация. Это означает, что возникновение исключения где либо в коде будет приводит к последовательному выходу из управляющих конструкций и функций до первого блока catch либо до функции main (с выдачей соответствующей ошибки в поток) основного скрипта
  • Возможность переопределения основного класса Exception через наследование
  • Возможность обработки нескольких типов исключений одновременно

Возможности обработки стандартных ошибок PHP крайне ограничены:

  • Можно заблокировать при помощи @
  • Можно установить свой обработчик при помощи set_error_handler
  • Можно сгенерировать свою ошибку при помощи trigger_error

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

  1. <?php
  2. class MyException extends Exception {
  3.    public function __construct($message, $errorLevel = 0, $errorFile = », $errorLine = 0) {
  4.       parent::__construct($message, $errorLevel);
  5.       $this->file = $errorFile;
  6.       $this->line = $errorLine;
  7.    }
  8. }
  9. set_error_handler(create_function(‘$c, $m, $f, $l’, ‘throw new MyException($m, $c, $f, $l);’), E_ALL);
  10. ?>

* This source code was highlighted with Source Code Highlighter.

Этот код необходимо вынести в отдельный файл и подключать его только один раз. Класс MyException расширяет стандартный класс Exception добавлением двух дополнительных параметров в конструктор: файла и номера строки с ошибкой.
Функция set_error_handler устанавливает в качестве обработчика ошибок динамически созданную lambda-функцию (callback), которая и генерирует исключение в случае возникновения ошибки. Особенно прошу обратить внимание на второй параметр функции set_error_handler. Этот параметр очень важен, так как он определяет для каких типов ошибок будет вызываться пользовательский (то есть наш) обработчик, а для каких стандартный. В данном примере, я установил значение E_ALL, что означает, что обработчик будет вызываться для всех типов ошибок.
Если мы не хотим обрабатывать некоторые типы ошибок, например, Notice, то мы можем запросто указать это:

set_error_handler(create_function(‘$c, $m, $f, $l’, ‘throw new MyException($m, $c, $f, $l);’), E_ALL & ~E_NOTICE);* This source code was highlighted with Source Code Highlighter.

Однако идеальным подходом, как мне кажется будет все таки обработка всех ошибок, но для некоторых типов, в частности, notice, было бы целесообразно не выкидывать exception, а просто выводить информацию на экран:

set_error_handler(create_function(‘$c, $m, $f, $l’, ‘if ($c === E_NOTICE) {echo ‘This is notice: ‘.$m} else {throw new MyException($m, $c, $f, $l);}’), E_ALL);* This source code was highlighted with Source Code Highlighter.

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

  1. Выводить все ошибки одновременно, а не по одной
  2. Отделить обработку ошибок валидации от обработки прочих исключений

Решение:
Главной сложностью здесь для нас будет то самое пресловутое преимущество Exception, которое заключается в том, что при «бросании» исключения происходит выход из управляющих конструкций до первого блока catch (или до конца скрипта). Для того, чтобы обойти этот подводный камень определим новый класс-потомок FormFieldsListException, в котором реализуем механизм накопления ошибок, а «бросать» исключение будем только после валидации всех полей. В классе FormFieldsListException определяем защищенный (protected) член $_list, в котором будем хранить данные. Для упрощения работы с массивом $_list указываем, что класс будет реализовывать два интерфейса: ArrayAccess для доступа к элементам массива и Iterator для работы в цикле. При инициализации метода проверки валидации создаем объект FormFieldsListException, а затем по мере определения ошибок добавляем их в объект FormFieldsListException, как в обычный массив.

  1. <?php
  2. class FormFieldsListException extends Exception implements ArrayAccess, Iterator {
  3.   protected $_list = array();
  4.   
  5.   public function __construct() {
  6.   }
  7.   
  8.   public function offsetExists($index) {
  9.     return isset($this->_list[$index]);
  10.   }
  11.   
  12.   public function offsetGet($index) {
  13.     return $this->_list[$index];
  14.   }
  15.   
  16.   public function offsetSet($index, $value) {
  17.     if (isset($index)) {
  18.       $this->_list[$index] = $value;
  19.     }
  20.     else {
  21.       $this->_list[] = $value;
  22.     }
  23.   }
  24.   
  25.   public function offsetUnset($index) {
  26.     unset($this->_list[$index]);
  27.   }
  28.   
  29.   public function current() {
  30.     return current($this->_list);
  31.   }
  32.   
  33.   public function key() {
  34.     return key($this->_list);
  35.   }
  36.   
  37.   public function next() {
  38.     return next($this->_list);
  39.   }
  40.   
  41.   public function rewind() {
  42.     return reset($this->_list);
  43.   }
  44.   
  45.   public function valid() {
  46.     return (bool) $this->current();
  47.   }
  48. }
  49. ?>

* This source code was highlighted with Source Code Highlighter.

После окончания процедуры валидации проверяем были ли занесены какие-то сообщения об ошибках. Если да то «бросаем» подготовленный объект исключения.
Для отлова исключения используем два блока catch: для FormFieldsListException и для всех остальных исключений. Это позволяет задать различные виды действий при возникновении различных типов исключений.

  1. <?php
  2. function validateForm() {
  3.    $e = new FormFieldsListException();
  4.    if ($errorInFirstField) {
  5.       $e[] = ‘Error in first field’;
  6.    }
  7.    if ($errorInSecondField) {
  8.       $e[] = ‘Error in second field’;
  9.    }
  10.    if ((bool)$e->current()) {
  11.       throw $e;
  12.    }
  13. }
  14.  
  15. try {
  16.    validateForm();
  17. }
  18. catch (FormFieldsListException $error) {
  19.    echo ‘<b>Errors in the fields</b>:<br />’;
  20.    foreach ($error as $e) {
  21.       echo $e.‘<br />’;
  22.    }
  23. }
  24. catch (Exception $error) {
  25.    echo ‘Not validation error! ‘.$error->getMessage();
  26. }
  27. ?>

* This source code was highlighted with Source Code Highlighter.

Вот так вот! :)
Правильно спроектированная система исключений способна серьезно упростить жизнь программиста, особенно при разработке приложений с использованием шаблона MVC. Как показало это небольшое исследование система обработки исключений в PHP5 таит в себе немалые резервы для модернизации и использования в специфических ситуациях.
P.S.: Некоторые из программистов, которым я показывал данную статью, считают использование исключений для валидации форм, мягко говоря, не самым лучшим вариантом (кстати, я бы попросил читателей, которые «в теме» высказаться по этому поводу), поэтому прошу считать приведенный пример всего лишь учебным примером, а не руководством к действию.
P.P.S.: Огромное спасибо товарищу ashofthedream, спор с которым и натолкнул меня на мысль изучить исключения поподробнее.

UPD: Перенесено в блог PHP

Хотя PHP уже давно поддерживает обработку исключений, однако, по сравнению с Java эта поддержка была довольно слабой

Первоначальная поддержка обработки исключений была введена в язык с 5 версии PHP, с двумя простыми встроенными классами исключений — Exception и ErrorException, с поддержкой дополнительных классов через SPL. Идея этого поста состоит в том, чтобы представить читателям современные возможности обработки исключений PHP. 

Новый интерфейс

Хотя PHP 7 предоставляет классы Error и Exception, давайте сначала затронем интерфейс Throwable . И Error и Exception классы реализуют Throwable интерфейс — это основа для любого объекта , который может быть брошен с помощью оператора throw. Единственное, что он не может быть реализован непосредственно в классах пользовательского пространства, только через расширение класса Exception. Кроме того, он обеспечивает единую точку для отлова обоих типов ошибок в одном выражении:

<?php

try {
// ваш код
} catch (Throwable $e) {
echo 'Очень хороший способ отловить исключения и ошибки';
}

Список доступных встроенных классов исключений начиная с PHP 7.4:

  • Exception
  • ErrorException
  • Error
  • ArgumentCountError
  • ArithmeticError
  • AssertionError
  • DivisionByZeroError
  • CompileError
  • ParseError
  • TypeError

Дополнительные классы исключений можно найти в стандартной библиотеке PHP . И наиболее заметным из расширений JSON является класс JsonException.

THROWABLE

Интерфейс Throwable  PHP 7:

interface Throwable
{
public function getMessage(): string; // Error reason
public function getCode(): int; // Error code
public function getFile(): string; // Error begin file
public function getLine(): int; // Error begin line
public function getTrace(): array; // Return stack trace as array like debug_backtrace()
public function getTraceAsString(): string; // Return stack trace as string
public function getPrevious(): Throwable; // Return previous `Trowable`
public function __toString(): string; // Convert into string
}

Вот иерархия Throwable:

interface Throwable
|- Error implements Throwable
|- ArithmeticError extends Error
|- DivisionByZeroError extends ArithmeticError
|- AssertionError extends Error
|- ParseError extends Error
|- TypeError extends Error
|- ArgumentCountError extends TypeError
|- Exception implements Throwable
|- ClosedGeneratorException extends Exception
|- DOMException extends Exception
|- ErrorException extends Exception
|- IntlException extends Exception
|- LogicException extends Exception
|- BadFunctionCallException extends LogicException
|- BadMethodCallException extends BadFunctionCallException
|- DomainException extends LogicException
|- InvalidArgumentException extends LogicException
|- LengthException extends LogicException
|- OutOfRangeException extends LogicException
|- PharException extends Exception
|- ReflectionException extends Exception
|- RuntimeException extends Exception
|- OutOfBoundsException extends RuntimeException
|- OverflowException extends RuntimeException
|- PDOException extends RuntimeException
|- RangeException extends RuntimeException
|- UnderflowException extends RuntimeException
|- UnexpectedValueException extends RuntimeException

Ошибка, почему?

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

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

Вот пример ловли фатальной ошибки в PHP 7.1. Обратите внимание, что нефатальная ошибка не обнаружена.

<?php 

try {
// будет генерировать уведомление, которое не будет поймано
echo $someNotSetVariable;
// фатальная ошибка, которая сейчас на самом деле ловится
someNoneExistentFunction();
} catch (Error $e) {
echo "Error caught: " . $e->getMessage();
}

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

Notice: Undefined variable: someNotSetVariable on line 3
Error caught: Call to undefined function someNoneExistentFunction()

Константы ошибок

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

Вот некоторые из наиболее часто встречающихся кодов ошибок:

  • E_DEPRECATED — интерпретатор сгенерирует этот тип предупреждений, если вы используете устаревшую языковую функцию. Сценарий обязательно продолжит работать без ошибок.
  • E_STRICT — аналогично E_DEPRECATED, — указывает на то, что вы используете языковую функцию, которая не является стандартной в настоящее время и может не работать в будущем. Сценарий будет продолжать работать без каких-либо ошибок.
  • E_PARSE — ваш синтаксис не может быть проанализирован, поэтому ваш скрипт не запустится. Выполнение скрипта даже не запустится.
  • E_NOTICE — движок просто выведет информационное сообщение. Выполнение скрипта не прервется, и ни одна из ошибок не будет выдана.
  • E_ERROR — скрипт не может продолжить работу, и завершится. Выдает ошибки, а как они будут обрабатываться, зависит от обработчика ошибок.
  • E_RECOVERABLE_ERROR — указывает на то, что, возможно, произошла опасная ошибка, и движок работает в нестабильном состоянии. Дальнейшее выполнение зависит от обработчика ошибок, и ошибка обязательно будет выдана.

Полный список констант можно найти в руководстве по PHP.

Функция обработчика ошибок

Функция set_error_handler() используется, чтобы сообщить PHP как обрабатывать стандартные ошибки, которые не являются экземплярами класса исключений Error. Вы не можете использовать функцию обработчика ошибок для фатальных ошибок. Исключения ошибок должны обрабатываться с помощью операторов try/catch. set_error_handler() принимает callback функцию в качестве своего параметра. Callback-функции в PHP могут быть заданы двумя способами: либо строкой, обозначающей имя функции, либо передачей массива, который содержит объект и имя метода (именно в этом порядке). Вы можете указать защищенные и приватные методы для callable в объекте. Вы также можете передать значение null, чтобы указать PHP вернуться к использованию стандартного механизма обработки ошибок. Если ваш обработчик ошибок не завершает программу и возвращает результат, ваш сценарий будет продолжать выполняться со строки, следующей за той, где произошла ошибка.

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

Вот пример:

<?php

function myCustomErrorHandler(int $errNo, string $errMsg, string $file, int $line) {
echo "Ух ты, мой обработчик ошибок получил #[$errNo] в [$file] на [$line]: [$errMsg]";
}

set_error_handler('myCustomErrorHandler');

try {
why;
} catch (Throwable $e) {
echo 'И моя ошибка: ' . $e->getMessage();
}

Если вы запустите этот код в PHP-консоли php -a, вы должны получить похожий вывод:

Error #[2] occurred in [php shell code] at line [3]: [Use of undefined constant why - assumed 'why' (this will throw an Error in a future version of PHP)]

Самые известные PHP библиотеки , которые делают обширное использование РНР set_error_handler() и могут сделать хорошие представления исключений и ошибок являются whoops,  и Symony Debug, ErrorHandler компоненты. Я смело рекомендую использовать один из них. Если не собираетесь их использовать в своем проекте, то вы всегда можете черпать вдохновение из их кода. В то время как компонент Debug широко используется в экосистеме Symfony, Whoops остается библиотекой выбора для фреймворка Laravel . 

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

Отображение или подавление нефатальной ошибки

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

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

Для этого вам нужно настроить PHP, используя следующие параметры в вашем файле php.ini:

  • display_errors – может быть установлен в false для подавления сообщений
  • log_errors – может использоваться для хранения сообщений об ошибках в файлах журнала
  • error_reporting – можно настроить, какие ошибки вызывают отчет

Лучше всего корректно обрабатывать ошибки в вашем приложении. В производственном процессе вы должны скорее регистрировать необработанные ошибки, чем разрешать их отображение пользователю. Функция error_log() может использоваться для отправки сообщения одной из определенных процедур обработки ошибок. Вы также можете использовать функцию error_log() для отправки электронных писем, но лично вы бы предпочли использовать хорошее решение для регистрации ошибок и получения уведомлений при возникновении ошибок, например Sentry или Rollbar .

Существует вещь, называемая оператором контроля ошибок ( @ ), который по своей сути может игнорировать и подавлять ошибки. Использование очень простое — просто добавьте любое выражение PHP с символом «собаки», и сгенерированная ошибка будет проигнорирована. Хотя использование этого оператора может показаться интересным, я призываю вас не делать этого. Мне нравится называть это живым пережитком прошлого.

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

Исключения (Exceptions)

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

Исключение будет возникать в стеке вызовов, если вы его не перехватите. Давайте посмотрим на простой пример:

try {
print "это наш блок попыток n";
throw new Exception();
} catch (Exception $e) {
print "что-то пошло не так, есть улов!";
} finally {
print "эта часть всегда выполняется";
}

PHP включает в себя несколько стандартных типов исключений, а стандартная библиотека PHP (SPL) включает в себя еще несколько. Хотя вам не нужно использовать эти исключения, это означает, что вы можете использовать более детальное обнаружение ошибок и отчеты. Классы Exception и Error реализуют интерфейс Throwable и, как и любые другие классы, могут быть расширены. Это позволяет вам создавать гибкие иерархии ошибок и адаптировать обработку исключений. Только класс, который реализует класс Throwable, может использоваться с ключевым словом throw. Другими словами, вы не можете объявить свой собственный базовый класс и затем выбросить его как исключение.

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

Ловля исключений

Вы должны использовать try/catch структуру:

<?php

class MyCustomException extends Exception { }

function throwMyCustomException() {
throw new MyCustomException('Здесь что-то не так.');
}

try {
throwMyCustomException();
} catch (MyCustomException $e) {
echo "Ваше пользовательское исключение поймано";
echo $e->getMessage();
} catch (Exception $e) {
echo "Стандартное исключение PHP";
}

Как видите, есть два предложения catch. Исключения будут сопоставляться с предложениями сверху вниз, пока тип исключения не будет соответствовать предложению catch. Эта очень простая функция throwMyCustomException() генерирует исключение MyCustomException, и мы ожидаем, что оно будет перехвачено в первом блоке. Любые другие исключения, которые произойдут, будут перехвачены вторым блоком. Здесь мы вызываем метод getMessage() из базового класса Exception. Вы можете найти больше информации о дополнительном методе в Exception PHP docs.

Кроме того, можно указать несколько исключений, разделяя их трубой ( | ).

Давайте посмотрим на другой пример:

<?php

class MyCustomException extends Exception { }
class MyAnotherCustomException extends Exception { }

try {
throw new MyAnotherCustomException;
} catch (MyCustomException | MyAnotherCustomException $e) {
echo "Caught : " . get_class($e);
}

Этот очень простой блок catch будет перехватывать исключения типа MyCustomException и MyAnotherCustomException.

Немного более продвинутый сценарий:

// exceptions.php
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

try {
throw new NotFoundHttpException();
} catch (\Exception $e) {
echo 1;
} catch (NotFoundHttpException $e) {
echo 2;
} catch (\Exception $e) {
echo 3;
} finally {
echo 4;
}

Это ваш окончательный ответ?

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

<?php

class MyCustomException extends Exception { }

function throwMyCustomException() {
throw new MyCustomException('Здесь что-то не так');
}

try {
throwMyCustomException();
} catch (MyCustomException $e) {
echo "Ваше пользовательское исключение поймано ";
echo $e->getMessage();
} catch (Exception $e) {
echo "Стандартное исключение PHP";
} finally {
echo "Я всегда тут";
}

Вот хороший пример того, как работают операторы PHP catch/finally:

<?php

try {
try {
echo 'a-';
throw new exception();
echo 'b-';
} catch (Exception $e) {
echo 'пойманный-';
throw $e;
} finally {
echo 'завершенный-';
}
} catch (Exception $e) {
echo 'конец-';
}

Функция обработчика исключений

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

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

Функция restore_exception_handler() вернет обработчик исключений к его предыдущему значению.

<?php

class MyCustomException extends Exception { }

function exception_handler($exception) {
echo "Uncaught exception: " , $exception->getMessage(), "\n";
}

set_exception_handler('exception_handler');

try {
throw new Exception('Uncaught Exception');
} catch (MyCustomException $e) {
echo "Ваше пользовательское исключение поймано ";
echo $e->getMessage();
} finally {
echo "Я всегда тут";
}

print "Не выполнено";

Здесь простая функция exception_handler будет выполняться после блока finally, когда ни один из типов исключений не был сопоставлен. Последний вывод никогда не будет выполнен.

Для получения дополнительной информации обратитесь к документации PHP. 

Старый добрый «T_PAAMAYIM_NEKUDOTAYIM»

Вероятно, это было самое известное сообщение об ошибке PHP. В последние годы было много споров по этому поводу. Вы можете прочитать больше в отличном сообщении в блоге от Фила Осетра .  

Сегодня я с гордостью могу сказать, что если вы запустите этот код с PHP 7, то сообщение о T_PAAMAYIM_NEKUDOTAYIM больше не будет:

<?php

class foo
{
static $bar = 'baz';
}

var_dump('foo'::$bar);

// Output PHP < 7.0:
// PHP Parse error: syntax error, unexpected '::' (T_PAAMAYIM_NEKUDOTAYIM) in php shell code on line 1
// Output PHP > 7.0:
// string(3) "baz"
?>

В заключении

Со времени первого внедрения обработки исключений в PHP прошло много лет, пока мы не получили гораздо более надежную и зрелую обработку исключений, чем в Java. Обработка ошибок в PHP 7 получила много внимания, что делает его хорошим, открывая пространство для будущих улучшений, если мы действительно с сегодняшней точки зрения нуждаемся в них.

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

Курс PHP для начинающих

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

Внимание! Данный курс устарел!
Переходите к новому курсу «PHP для начинающих».

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

Курс PHP для начинающих

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

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

Ошибки и исключительные ситуации

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

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

Ниже приведен пример кода, который содержит некоторые средства обнаружения ошибок в том виде, в каком они могло быть реализованы в версии PHP 4 или в одной из предыдущих версий. В этом коде осуществляется выборка переменной POST, содержащей идентификатор пользователя. Такой идентификатор должен иметь длину по меньшей мере девять символов и начинаться с префикса «usr»:

Пример метода обработки ошибок без использования исключений

<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Основы PHP</title>
</head>

<body>

<?php

// Выполнить выборку идентификатора пользователя, подлежащего проверке
$user_id = isset($_POST['user_id']) ? $_POST['user_id'] : '';

// Подготовить для вывода на дисплей сообщение, содержимое которого
// зависит от того, является ли действительным идентификатор пользователя
if (!empty($user_id))
	echo !is_valid_user($user_id) ? '<b style="color:red;">Некорректный логин</b><br>' : "Все правильно<br>";


function is_valid_user($user_id)
{	
	// Возвратить значение false, если идентификатор пользователя 
	// не начинается с подстроки "usr"
	$pre_str = "usr";
	if ((strpos($user_id, $pre_str) === false) || (strpos ($user_id, $pre_str) != 0))
		return false;
		
	// Возвратить значение false, если идентификатор пользователя 
	// не соответствует требованиям по длине 
	if ((strlen($user_id) < 9)) return false;
	
	return true;
}

?>

<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
    <input type="text" name="user_id" placeholder="Введите логин"><br>
    <input type="submit" value="Отправить">
</form>

</body>
</html>

Вполне очевидно, что функция is_valid_user() может вернуть значение false вследствие несоблюдения любого из указанных требований. Применение исключений позволяет проще организовать выявление ошибок различных типов, после чего принимать соответствующие меры, учитывая характер каждой отдельной ошибки.

Класс Exception

В версии PHP 5 предусмотрен новый класс Exception, который может непосредственно использоваться в любом коде, выполняемом под управлением сервера PHP 5 или более поздней версии. В примере, приведенном выше, применялись булевы функции, а класс Exception позволяет создавать или активизировать в коде исключительные ситуации — экземпляры этого класса.

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

Код PHP

<?php

// Выполнить выборку идентификатора пользователя, подлежащего проверке
$user_id = isset($_POST['user_id']) ? $_POST['user_id'] : '';

// Подготовить для вывода на дисплей сообщение, содержимое которого
// зависит от того, является ли действительным идентификатор пользователя
try
{
	if (!empty($user_id) && is_valid_user($user_id))
		echo "Все правильно<br>";
}
catch (Exception $ex)
{
	$msg = $ex->getMessage();
	echo $msg;
}


function is_valid_user($user_id)
{	
	// Активизировать исключение, если идентификатор пользователя 
	// не начинается с подстроки "usr"
	$pre_str = "usr";
	if ((strpos($user_id, $pre_str) === false) || (strpos ($user_id, $pre_str) != 0))
		throw new Exception('<b style="color:red;">Некорректный логин - должен начинаться со строки "usr"</b><br>');
		
	// Активизировать исключение, если идентификатор пользователя 
	// не соответствует требованиям по длине 
	if ((strlen($user_id) < 9)) 
		throw new Exception('<b style="color:red;">Некорректный логин - длина меньше 9 символов</b><br>');
	
	return true;
}

?>

Использование исключений

В этом коде для активизации исключения применяется оператор throw. А сами средства обработки исключительных ситуаций в данном случае использовались для создания нового объекта Exception. Благодаря применению этих средств появляется возможность обрабатывать ошибки, нарушающие нормальный ход выполнения программы, как отдельные исключительные ситуации, поэтому ошибки больше не могут влиять на остальную часть приложения.

Перехват и обработка исключительных ситуаций осуществляется с помощью управляющей конструкции try/catch. Прежде всего необходимо включить любой код, выполнение которого может привести к возникновению ошибки или исключительной ситуации, в конструкцию try(). При активизации в этом коде любой исключительной ситуации выполнение блока try() прекращается; это означает, что оставшийся код в конструкции try() не выполняется. Затем блок catch() просматривается для поиска исключительной ситуации соответствующего типа, а обработка исключительной ситуации происходит с помощью кода, содержащегося в данном конкретном блоке catch. Благодаря такой организации работы может быть предусмотрен анализ различных условий с учетом типа активизированной исключительной ситуации, но не предпринимается безнадежная попытка справиться с какой-то общей, неконкретизированной ошибкой.

Активизация исключительной ситуации

Благодаря использованию исключительных ситуациях появляется еще одна удобная возможность — предоставлять пользователю настолько много (или настолько мало) информации об ошибке, насколько это целесообразно. В сочетании с объектом Exception допускается применение нескольких методов обработки исключительных ситуаций, которые могут использоваться для создания собственных сообщений об ошибках или организации правильных действий в сложившейся ситуации.

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

Код PHP

try
{
	if (!empty($user_id) && is_valid_user($user_id))
		echo "Все правильно<br>";
}
catch (Exception $ex)
{
	// Входная строка, переданная объекту 
	$msg = $ex->getMessage(); 
	
	// Определяемый пользователем код ошибки 
	$code = $ex->getCode();
	
	// Имя файла, при обработке которого была активизирована 
	// исключительная ситуация 
	$file = $ex->getFile();
	
	// Номер строки, при обработке которой возникла исключительная ситуация 
	$line = $ex->getLine();
	
	echo "Ошибка в коде $code: $msg Файл: $file, номер строки: $line<br><br>";
}

Получение дополнительной информации об исключении

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

Определение собственных подклассов Exception

Язык PHP позволяет также определять собственные классы, которые наследуют методы класса Exception. Это означает, что можно больше не ограничиваться использованием лишь функции getMessage() для получения информации о том, какой именно тип имеет возникшая ошибка. Подклассы могут быть определены так, как показано в следующем примере:

Код PHP

class CustomException extends Exception {
	public function __construct($message) {
		parent::__construct($message);
	}
}

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

Код PHP

<?php

// Определить специализированные классы исключений
class PrefixException extends Exception {
	public function __construct($message) {
		parent::__construct($message);
	}
}

class MaxLengthException extends Exception {
	public function __construct($message) {
		parent::__construct($message);
	}
}

// Выполнить выборку идентификатора пользователя, подлежащего проверке
$user_id = isset($_POST['user_id']) ? $_POST['user_id'] : '';

// Подготовить для вывода на дисплей сообщение, содержимое которого
// зависит от того, является ли действительным идентификатор пользователя
try
{
	if (!empty($user_id) && is_valid_user($user_id))
		echo "Все правильно<br>";
}
catch (PrefixException $ex)
{
	// Если префикс "usr" отсутствует, добавить его
	$user_id = "usr".$user_id;
	
	// Повторный вызов проверки
	try {
		if (is_valid_user($user_id))
			echo "Все правильно<br>";
	} catch (Exception $ex) { 
		echo $ex->getMessage();
	}
}
catch (MaxLengthException $ex)
{
	$msg = $ex->getMessage(); 
	echo $msg;
}


function is_valid_user($user_id)
{	
	// Активизировать исключение, если идентификатор пользователя 
	// не начинается с подстроки "usr"
	$pre_str = "usr";
	if ((strpos($user_id, $pre_str) === false) || (strpos ($user_id, $pre_str) != 0))
		throw new PrefixException('<b style="color:red;">Некорректный логин - должен начинаться со строки "usr"</b><br>');
		
	// Активизировать исключение, если идентификатор пользователя 
	// не соответствует требованиям по длине 
	if ((strlen($user_id) < 9)) 
		throw new MaxLengthException('<b style="color:red;">Некорректный логин - длина меньше 9 символов</b><br>');
	
	return true;
}

?>

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

Ограничения средств обработки исключений языка PHP

Объект Exception — новое средство, появившееся в версии PHP 5, поэтому этот объект, как таковой, все еще находится на самых ранних этапах разработки. Ко времени написания этих строк язык PHP не поддерживал использование таких конструкций, как finally() и throws(), предусмотренных в языке Java и других языках. Кроме того, в отличие от других языков на исключительные ситуации еще не отображаются ошибки, распознаваемые самим интерпретатором PHP (в том числе ошибки, сообщения о которых обычно появляются в клиентском браузере). Из-за этого, например, возникновение ошибки в операторе SQL, выполняемом в блоке try/catch, не приводит автоматически к активизации исключительной ситуации, которую можно было бы перехватить и заняться ее обработкой. Такие удобные функциональные средства, по всей вероятности, должны быть включены в одну из будущих версий PHP, поэтому для авторов имеет смысл упомянуть о них, а для читателей — следить за их появлением.

Cookie-файлы и сеансы

Нарушения в работе системы PHP

Оценить статью:

Понравилась статья? Поделить с друзьями:
  • Обработка ошибок ввода python
  • Обработка ошибок ввода java
  • Обработка ошибок вба
  • Обработка ошибок баз данных delphi
  • Обработка ошибок vbs