Throw new error php

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

Исключения

Содержание

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

В 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


<?phpclass SpecificException extends Exception {}

function

test() {
do_something_risky() or throw new Exception('Всё сломалось');
}

try {

test();
} catch (
Exception $e) {
print
$e->getMessage();
}
?>

ask at nilpo dot com

13 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:xampphtdocsCustomExceptionCustomException.php(31)
#0 C:xampphtdocsCustomExceptionExceptionTest.php(19): CustomException->__construct()
#1 C:xampphtdocsCustomExceptionExceptionTest.php(43): exceptionTest()
#2 {main}


Johan

11 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

5 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).


Simo

7 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.

Edu

9 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 inn";
                throw
$e;
        } finally {
                echo
$e->getMessage()." finally n";
                throw new
Exception("Bye");
        }
} catch (
Exception $e) {
        echo
$e->getMessage()." catch outn";
}
?>

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 );
}
?>


mlaopane at gmail dot com

4 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

8 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.

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/>";
   }
?>


lscorionjs at gmail dot com

24 days ago


<?phptry {
 
$str = 'hi';
  throw new
Exception();
} catch (
Exception) {
 
var_dump($str);
} finally {
 
var_dump($str);
}
?>

Output:
string(2) "hi"
string(2) "hi"

ilia-yats at ukr dot net

1 month 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.


Daan

1 year ago


I would like to emphasise that you can not rethrow an Exception inside a catch-block and expect that the next catch-block will handle it.

<?php try {
    throw new
RuntimeException('error');           
} catch (
RuntimeException $e) {
    throw
$e;
} catch (
Exception $e) {
   
// this will not be executed[
}
?>


Я рад бы написать что “эта статья предназначена для новичков”, но это не так. Большинство php-разработчиков, имея опыт 3, 5 и даже 7 лет, абсолютно не понимают как правильно использовать эксепшены. Нет, они прекрасно знают о их существовании, о том что их можно создавать, обрабатывать, и т.п., но они не осознают их удобность, логичность, и не воспринимают их как абсолютно нормальный элемент разработки.

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

Почему мы не умеем пользоваться эксепшенами:

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

PHP — это чрезмерно любящая мать. И при отсутствии строгого отца (например, Java) или самодисциплины, разработчик вырастет эгоистом, которому плевать на все правила, стандарты и лучшие практики. И вроде бы E_NOTICE пора включать, а он все на мать надеется. Которая, между прочим, стареет — ей уже E_STRICT c E_DEPRICATED нужны, а сынуля все на шее висит.

Виноват ли PHP — предмет дискуссий, но то, что с самого начала PHP не приучает нас к эксепшенам — это факт: его стандартные функции не создают эксепшены. Они либо возвращают false, намекая что что-то не так, или записывают куда-то код ошибки, который не всегда додумаешься проверить. Или впадают в другую крайность — Fatal Error.

И пока наш начинающий разработчик пытается написать свою первую быдло-cms, он ни разу не встретиться с механизмом эксепшенов. Вместо этого, он придумает несколько способов обработки ошибок. Я думаю, все понимают о чем я — эти методы, возвращающие разные типы (например, объект при успешном выполнении, а при неудаче — строка с ошибкой), или запись ошибки в какую-либо переменную/свойство, и всегда — куча проверок чтоб передать ошибку вверх по стеку вызовов.

Затем он начнет использовать сторонние библиотеки: попробует, например, Yii, и впервые столкнется с эксепшенами. И вот тогда…

И тогда ничего не произойдет. Ровном счетом ничего. У него уже сформировались отточенные месяцами/годами способы обработки ошибок — он продолжит использовать их. Вызванный кем-то (сторонней библиотекой) эксепшн будет восприниматься как определенный вид Fatal Error. Да, гораздо более детальный, да подробно логируется, да Yii покажет красивую страничку, но не более.

Затем он научиться отлавливать и обрабатывать их. И на этом его знакомство c эксепшенами закончиться. Ведь надо работать, а не учиться: знаний ему и так хватает (сарказм)!

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

Преимущества эксепшенов

На самом деле использование эксепшенов — крайне лаконичное и удобное решение создания и обработки ошибок. Приведу наиболее значимые преимущества:

Контекстная логика

Прежде всего, хотелось бы показать что эксепшн — это не всегда только ошибка (как обычно ее воспринимают разработчики). Иногда он может быть частью логики.

Например, есть у нас функция чтения JSON объекта из файла:

/**
 * Читает объект из JSON файла
 * @param string $file
 * @throws FileNotFoundException    файл не найден
 * @throws JsonParseException       не правильный формат json
 * @return mixed
 */
public function readJsonFile($file)
{
  ...
}

Допустим мы пытаемся прочитать какие-то ранее загруженные данные. При такой операции эксепшн FileNotFoundException не является ошибкой и вполне допустим: возможно, мы ни разу не загружали данные, поэтому файла и нет. А вот JsonParseException — это уже признак ошибки, ибо данные были заргужены, обработаны, сохранены в файл, но почему-то сохранились не правильно.

Совсем другое дело, когда мы пытаемся прочитать файл, который должен быть всегда: при такой операции FileNotFoundException так же является сигналом ошибки.

Таким образом, эксепшены позволяют нам определять логику их обработки в зависимости от контекста, что очень удобно.

Упрощение логики и архитектуры приложения

Попробуйте использовать эксепшены, и вы увидите как ваш код станет более лаконичным и понятным. Исчезнут все костыльные механизмы, возможно, уберутся куча вложенных if’ов, различные механизмы передачи ошибки вверх по стеку вызова, логика станет более простой и прямолинейной.

Места вызовы эксепшенов помогут вашему коллеге лучше понять бизнес логику и предметную область, ибо копаясь в вашем коде он сразу увидит что допустимо, а что нет.

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

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

Вот пример информативного интерфейса, который дополнен знаниями об эксепшенах:

interface KladrService
{
	/**
	 * Определяет код КЛАДР по адресу
	 * @param Address $address
	 * @return string код для адреса
	 * @throws AddressNotFoundException     адрес не найден в базе адресов
	 * @throws UnresoledAddressException    адрес найден, но для него не существует код КЛАДР
	 */
	public function resolveCode(Address $address);

	/**
	 * Определяет адрес по коду КЛАДР
	 * @param string $code
	 * @return Address
	 * @throws CodeNotFoundException    не найлен код КЛАДР
	 */
	public function resolveAddress($code);
}

Следует упомянуть что разрабатывая классы эксепшенов мы должны следовать принципу информативного интерфейса. Грубо говоря — учитывать их логический смысл, а не физический. Например, если адреса у нас храняться в файлах, то отсутствие файла адреса вызовет FileNotFoundException. Мы же должны перехватить его и вызвать более осмысленный AddressNotFoundException.

Использование объектов

Использование определенного класса в качестве ошибки — очень удобное решение. Во первых, класс невозможно перепутать: взгляните на 2 способа обработки ошибки:

if(Yii::app()->kladr->getLastError() == ‘Не найден адрес’){
	….
}

try{
	...
}
catch(AddressNotFoundException $e){
	...
}

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

Второе преимущество — класс эксепшена инкапсулирует все необходимые данные для его обработки. Например, AddressNotFoundException мог бы выглядеть следующим образом:

/**
 * Адрес не найден в базе адресов
 */
class AddressNotFoundException extends Exception
{
	/**
	 * Не найденный адрес
	 * @var Address
	 */
	private $address;

	/**
	 * @param Address $address
	 */
	public function __construct(Address $address)
	{
		Exception::__construct('Не найден адрес '.$address->oneLine);
		$this->address = $address;
	}
	/**
	 * @return Address
	 */
	public function getAddress()
	{
		return $this->address;
	}
}

Как видим — эксепшн содержит адрес, который не удалось найти. Обработчик может его получить и выполнить на основании его какую-то свою логику.

Третье преимущество — это, собственно, все преимущества ООП. Хотя эксепшены, как правило, простые объекты, поэтому возможности ООП мало используются, но используются.

Например, у меня в приложении порядка 70 классов эксепшенов. Из них несколько — базовых — по одному классу на модуль. Все остальные — наследуются от базового класса своего модуля. Сделано это для удобства анализа логов.

Так же я использую несколько ИНТЕРФЕЙС-МАРКЕРОВ:

  • UnloggedInterface: По умолчанию у меня логируются все необработанные ошибки. Этим интерфейсом я помечаю эксепшены, которые не надо логировать вообще.
  • PreloggedInterface: Этим интерфейсом я помечаю эксепшены, которые необходимо логировать в любом случае: неважно, обработаны они или нет.
  • OutableInterface: Этот интерфейс помечает эксепшены, текст которых можно выдавать пользователю: далеко не каждый эксепшн можно вывести пользователю. Например, можно вывести эксепшн с текстом “Страница не найдена” — это нормально. Но нельзя выводить эксепшн с текстом “Не удалось подключиться к Mysql используя логин root и пароль 123”. OutableInterface помечает эксепшены которые выводить можно (таких у меня меньшинство). В остальных ситуация выводиться что то типа “Сервис не доступен”.

Обработчик по умолчанию, логирование

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

Этот обработчик позволяет нам выполнить различные действия перед остановкой скрипта. Самое главное что нужно сделать, это:

Откат изменений: так как операция не выполнена до конца, необходимо откатить все сделанные изменения. В противном случае мы испортим данные. Например, можно в CController::beforeAction() открыть транзакцию, в CController::afterAction() коммитить, а в случае ошибки сделать роллбэк в обработчике по умолчанию.

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

public function addPosition(Position $position)
{
  try
  {
    ... выполнение операции ...	
  }
  catch(Exception $e)
  {
    ... откат изменений ...
    
    throw $e;   // Заново бросаем тот же эксепшн
  }
}

Получается что мы откатили изменения и бросили тот же эксепшн, что продолжить его обработку.

Логирование: так же обработчик по умолчанию позволяет нам выполнить какое-то кастомное логирование. Например, в моем приложении я все складываю в базу и использую собственное средство для анализа. На работе мы используем getsentry.com/welcome. В любом случае, эксепшн, дошедший до обработчика по умолчанию — скорее всего непредусмотренный эксепшн, и его необходимо логировать. Следует отметить, что в класс эксепшена можно добавить различную информацию, которую необходимо логировать для большего понимания причины возникновения ошибки.

Невозможность не заметить и перепутать

Огромным плюсом эксепшена является его однозначность: его не возможно не заметить и не возможно с чем-то спутать.

Из первого следует что мы всегда будем осведомлены о возникшей ошибке. И это замечательно — всегда лучше знать о проблеме, чем не знать.

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

$result = $this->doAnything(); // null если не нашла нужный объект и false в случае ошибки

// Не заметит ошибки
if($result){ ... }

// Не заметит ошибки
if($result == null){ ... }

// Не заметит ошибки
if(empty($result)){ ... }

// Не заметит ошибки
if($result = null){ ... }

Эксепшн же невозможно пропустить.

Прекращение ошибочной операции

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

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

$this->doOperation();
if($this->getLastError() !== null)
{
    echo $this->getLastError(); 
    die;
}

Это требует определенной дисциплины от разработчиков. А далеко не все дисциплинированны. Далеко не все вообще знают что у вашего объекта есть метод getLastError(). Далеко не все понимают, почему вообще так важно проверит что все идет как надо, а если нет — откатить изменения и прекратить выполнение.

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

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

Когда следует вызывать эксепшены:

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

Встает вопрос: в каких ситуациях стоит вызывать эксепшн?

Если кратко — всегда! Если подробно: всегда, когда ты уверен что операция должна выполниться нормально, но что-то пошло не так, и ты не знаешь что с этим делать.

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

/**
 * Создает пост
 */
public function actionCreate()
{
  $post = Yii::app()->request->loadModel(new Post());
  if($post->save())
  {
    $this->outSuccess($post);
  }
  else
  {
    $this->outErrors($post);
  }
}

Когда мы введем некорректные данные поста эксепшн не вызывается. И это вполне соответствует формуле:

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

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

/**
 * Отменяет заказа.
 * Отмена производиться путем смены статуса на STATUS_CANCEL.
 * @throws Exception
 */
public function cancel()
{
  // Проверим, находиться ли STATUS_CANCEL в разрешенных
  if(!$this->isAllowedStatus(self::STATUS_CANCEL))
  {
    throw new Exception('Cancel status not allowed');
  }

  // Сообственно смена статуса
  $this->status = self::STATUS_CANCEL;
  $isSaved = $this->save();

  // Проверка на то что все успешно сохранилось и что после сохранения статус остался STATUS_CANCEL
  if(!$isSaved|| $this->status !== self::STATUS_CANCEL)
  {
    throw new Exception('Bad logic in order cancel');
  }
}

Сама кнопка отмены показывается только тогда, когда заказ возможно отменить. Таким образом, когда вызывается этот метод, я уверен, что операция должна пройти успешно (в противном случае кнопка бы не отобразилась, и пользователь не смог бы нажать на нее для вызова этого метода).

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

Далее идет выполнение операции и сохранение изменений.

Затем идет поствалидация — мы проверяем, действительно ли все сохранилось, и действительно ли статус изменился. На первый взгляд это может показаться бессмысленным, но: заказ вполне мог не сохранится (например, не прошел валидацию), а статус вполне мог быть изменен (например, кто-то набыдлокодил в CActiveRecord::beforeSave). Поэтому эти действия необходимы, и, опять-таки, если что-то пошло не так — бросаем эксепшн, так как в пределах данного метода мы не знаем как обрабатывать эти ошибки.

Эксепшн vs возврат null

Следует отметить, что эксепшн следует бросать только в случае ошибки. Я видел как некоторые разработчики злоупотребляют ими, бросая их там, где не следовало бы. Особенно часто — когда метод возвращает объект: если не получается вернуть объект — бросается эксепшн.

Тут следует обратить внимание на обязанности метода. Например, СActiveRecord::find() не бросает эксепшн, и это логично — уровень его “знаний” не содержит информации о том, является ли ошибкой отсутствие результата. Другое дело, например, метод KladrService::resolveAddress() который в любом случае обязан вернуть объект адреса (иначе либо код неправильный, либо база не актуальная). В таком случае нужно бросать эксепшн, ибо отсутствие результата — это ошибка.

В целом же, описанная формула идеально определяет места, где необходимо бросать эксепшены. Но особо хотелось бы выделить 2 категории эксепшенов, которых нужно делать как можно больше:

Технические эксепшены

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

Вот несколько примеров:

// В нескольких if
if($condition1)
{
	$this->do1();
}
elseif($condition2)
{
	$this->do2();
}

...

else
{
	// Когда должен сработать один из блоков if, но не сработал - бросаем эксепшн
	throw new BadLogicException;
}

// То же самое в swith
switch($c)
{
	case 'one':
		return 1;

	case 'two'
		return 2;

		...

	default:
		// Когда должен сработать один из блоков case, но не сработал - бросаем эксепшн
		throw new BadLogicException;
}

// При сохранении связанных моделей
if($model1->isNewRecord)
{
	// Если первая модель не сохранена, у нее нет id, то строка $model2->parent_id = $model1->id
	// сделает битые данные, поэтому необходимо проверять
	throw new BadLogicException;
}

$model2->parent_id = $model1->id;

// Просто сохранении - очень часто разраотчики используют save и не проверяют результат
if(!$model->save())
{
	throw new BadLogicException;
}

/**
 * Cкоуп по id пользователя
 * @param int $userId
 * @return $this
 */
public function byUserId($userId)
{
	if(!$userId)
	{
		// Если не вызывать этот эксепшн, то при пустом userId скоуп вообще не будет применен
		throw new InvalidArgumentException;
	}

	$this->dbCriteria->compare('userId', $userId);
	return $this;
}

Технические эксепшены помогут не допустить или отловить, имхо, большую часть багов в любом проекте. И неоспоримым плюсом их использования является отсутствие необходимости понимать предметную область: единственное что требуется — это дисциплина разработчика. Я призываю не лениться и вставлять такие проверки повсеместно.

Эксепшены утверждений

Эксепшены утверждений (по мотивам DDD) вызываются когда мы обнаруживаем что нарушается какая-либо бизнес-логика. Безусловно, они тесно связанна с знаниями предметной области.

Они бросаются когда мы проверяем какое-либо утверждение, и видим что результат проверки не соответствует ожидаемому.

Например, есть метод добавления позиции в заказ:

/**
 * Добовляет позицию в заказ
 * @param Position $position
 * @throws Exception
 */
public function addPosition(Position $position)
{
  $this->positions[] = $position;

  ... перерасчет стоимость позиций, доставки, скидок, итоговой стоимсоти ...

// проверям корректность рассчета
  if($this->totalCost != $this->positionsCost + $this->deliveryCost - $this->totalDiscounts)
  {
    throw new Exception('Cost recalculation error');
  }

  ... Обновление параметров доставки ...

// проверям можем ли мы доставить заказа с новой позицеей
  if(!Yii::app()->deliveryService->canDelivery($this))
  {
    throw new Exception('Cant delivery with new position')
  }

… прочие действия ...
}

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

Здесь можно подискутировать на тему необходимости подобных эксепшенов:
Например, можно написать тесты на методы перерасчета стоимости заказа, и проверка в теле метода — не более чем дублирование теста. Можно проверять возможность доставки заказа с новой позицией до добавления позиции (чтоб предупредить об этом пользователя, как минимум)

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

Поэтому в критичных местах такие эксепшены нужны однозначно.

Изменение логики для избегания эксепшна

Как я уже говорил, PHP разработчики боятся эксепшенов. Они боятся их появления, и боятся бросать их самостоятельно.

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

Вот пример: необходимо просто отобразить страницу по id (чтоб вы понимали — это реальный код из известного проекта)

/**
 * Отображает страницу по id
 * @param int $id
 */
public function actionView($id = 1)
{
  $page = Page::model()->findByPk($id) ?: Page::model()->find();
  $this->render('view', ['page' => $page]);
}

Несмотря на простейшую и понятную задачу — здесь совершенно дикая логика.
Мало того, что она может показать пользователю совершенно не то что надо, так она еще и маскирует наши баги:

  • если id не задан — берется id = 1. Проблема в том, что когда id не задан — это уже баг, ибо где-то у нас не правильно формируются ссылки.
  • Если страница не найдена — значит где-то у нас ссылка на несуществующую страницу. Это тоже, скорее всего, баг.

Такое поведение не приносит пользы ни пользователю, ни разработчикам. Мотивация такой реализации — показать хоть что-то, ибо 404 эксепшн — плохо.

Еще один пример:

/**
 * Выдает код кладра города
 * @param mixed $region
 * @param mixed $city
 * @return string
 */
public function getCityKladrCode($region, $city)
{
  if($сode = ... получение кода для города... )
  {
    return $сode;
  }

  return ... получение кода для региона ...
}

Тоже из реального проекта, и мотивация такая-же: вернуть хоть что-то, но не вызывать эксепшн, несмотря на то, что метод явно должен возвращать код города, а не региона.

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

Мое мнение — это недопустимо. Просто когда ты работаешь с большими деньгами (а я с ними работал довольно долго), вырабатывается определенные правила, и одно из них — прерывать операцию в случае любого подозрения на ошибку. Транзакция на 10 млн баксов: согласитесь, ее лучше отменить, чем перечислить деньги не тому человеку.

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

Собачки

Я почему-то думал что собачками уже никто не пользуется. Но недавно столкнулся с коллективом разработчиков, которые используют их повсеместно вместо проверки isset, поэтому решил написать и про них.

Собачки вместо isset используют для лаконичности кода:

@$policy->owner->address->locality;

против

isset($policy->owner->address) ? $policy->owner->address->locality : null;

Действительно, выглядит намного короче, и на первый взгляд результат такой же. Но! опасно забывать что собачка — оператор игнорирования сообщений об ошибках. И @$policy->owner->address->locality вернет null не потому-что проверит существование цепочки объектов, а потому-что просто проигнорирует возникшую ошибку. А это совершенно разные вещи.

Проблем в том, что помимо игнорирования ошибки Trying to get property of non-object (которое и делает поведение собачки похожим на isset), игнорируются все другие возможные ошибки.

PHP — это магический язык! При наличии всех этих магических методов (__get, __set, __call, __callStatic, __invoke и пр.) мы не всегда можем сразу понять что происходит на самом деле.

Например, еще раз взглянем на строку $policy->owner->address->locality. На первый взгляд — цепочка объектов, если присмотреться пристально — вполне может быть и так:

  • policy — модель CActiveRecord
  • owner — релейшен
  • address — геттер, который, например, обращается к какому-либо стороннему сервису
  • locality — аттрибут у

То есть простой строкой $policy->owner->address->locality мы на самом деле запускаем выполнение тысяч строк кода. И собачка перед это строкой скрывает ошибки в любой из этих строк.

Таким образом, столь необдуманное использование собачки потенциально создает огромное кол-во проблем.

Послесловие

Программирование — потрясающее занятие. На мой взгляд, оно похоже на сборку огромного конструктора LEGO. В самом начале перед тобой инструкция и россыпь мелких деталей. И вот, ты берешь инструкцию, по которой методично собираешь их в небольшие блоки, затем объеденяешь их в нечто большее, еще больше… И ты ловишь кайф от этого чертовски увлекательного процесса, ты ловишь кайф от того, насколько все логично и продуманно устроенно, насколько все эти детальки подходят друг к другу. И вот — перед тобой уже целый трактор, или самосвал. И это потрясающе!

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

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

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

Так что всем, кто прочел этот пост и подумал “что за бред”, “я все это знаю, но применять лень”, или “будет сосунок мне указывать” — я желаю совершить баг. Баг, за который оштрафуют или уволят. И тогда вы, возможно, вспомните этот пост, и задумаетесь: “возможно я и в правду что-то делаю не так”?

Совершите его как можно скорее. Ибо лучше один раз ошибиться, но прозреть, чем всю жизнь прожить быдлокоддером. Аминь.

Всем добра )

Содержание

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

Модель исключений (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 Worldn";
?>

Результат выполнения данного примера:

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 Worldn";
?>

Результат выполнения данного примера:

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 уже давно поддерживает обработку исключений, однако, по сравнению с 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 SymfonyComponentHttpKernelExceptionNotFoundHttpException;

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 получила много внимания, что делает его хорошим, открывая пространство для будущих улучшений, если мы действительно с сегодняшней точки зрения нуждаемся в них.

Summary: in this tutorial, you will learn about the Exception class in detail and how to throw a new exception in PHP.

Introduction to the Exception class

When encountering a situation from which you cannot recover, you can throw an exception.

An exception is an instance of the Exception class. Like other PHP objects, you use the new keyword to create an instance of the Exception class.

An Exception object has two main properties: a message and a numeric code. The message describes the exception. The numeric code is optional, which specifies the context for a specific exception.

When you create a new exception, you provide the mesage the optional numeric code. For example:

<?php $exception = new Exception('Invalid username or password', 100);

Code language: HTML, XML (xml)

The Exception object has the getMessage() and getCode() that returns the message and numeric code:

<?php $exception = new Exception('Invalid username or password', 100); $message = $exception->getMessage(); $code = $exception->getCode();

Code language: HTML, XML (xml)

The throw statement

In practice, you rarely assign an exception to a variable. Instead, you raise (or throw) the Exception object using the throw statement:

<?php throw new Exception('Invalid username or password', 100);

Code language: HTML, XML (xml)

The throw statement accepts an instance of the Exception class or the subclass of the Exception class. In fact, it accepts any object that implements the Throwable interface. Note that the Exception class also implements the Throwable interface.

When PHP encounters a throw statement, it immediately halts the code execution. Therefore, the code after the throw statement won’t execute.

Throwing an exception example

The following example defines a function called divide() that uses the the throw statement:

function divide($x, $y) { if (!is_numeric($x) || !is_numeric($y)) { throw new InvalidArgumentException('Both arguments must be numbers or numeric strings'); } if ($y == 0) { throw new Exception('Division by zero, y cannot be zero'); } return $x / $y; }

Code language: PHP (php)

How the define() function works.

  • First, throw the InvalidArgumentException exception if $x and $y are not numbers or numeric strings.
  • Second, throw the division by zero exception if $y is zero.
  • Third, return the division of $x and $y.

PHP built-in exception classes

The standard PHP library (SPL) provides many subclasses of the Exception class. For example, you can use the InvalidArgumentException when an argument of a function or method is not valid.

The following shows the exception classes in PHP 8.0:

Exception ClosedGeneratorException DOMException ErrorException IntlException JsonException LogicException BadFunctionCallException BadMethodCallException DomainException InvalidArgumentException LengthException OutOfRangeException PharException ReflectionException RuntimeException OutOfBoundsException OverflowException PDOException RangeException UnderflowException UnexpectedValueException SodiumException

Code language: plaintext (plaintext)

Summary

  • An exception is an instance of the Exception class that implements the Throwable interface.
  • Use the throw statement to raise an exception. The throw statement accepts an exception object that implements the Throwable interface.

Did you find this tutorial useful?

Этот урок переехал в мой гитхаб: https://github.com/codedokode/pasta/blob/master/php/exceptions.md — ниже представлена старая версия, потому советую перейти и прочитать новую.


Если ты изучаешь ООП, ты наверняка натыкался на исключения. В мануале PHP описаны команды try/catch/throw и finally (доступна начиная с PHP 5.5), но не объясняется толком как их использовать. Чтобы разобраться с этим, надо узнать почему они вообще были придуманы.

А придуманы они были, чтобы сделать удобную обработку ошибок.

Для примера представим, что мы пишем приложение для вывода списка пользователей из файла на экран. Допустим, код выглядит как-то так:

$file = './users.csv';

// Загружаем список пользователей из файла в массив
$users = loadUsersFromFile($file); 

// Выводим
foreach ($users as $user) {
    echo "{$user['name']} набрал {$user['score']} очковn";
}

Все ли тут верно? Нет, не все. Мы забыли сделать обработку ошибок. Файла может не существовать, к нему может не быть доступа, данные в нем могут быть в неверном формате. Хорошая программа, разумеется должна обрабатывать такие ситуации и выводить соответствующее сообщение.

Самый простой (но плохой) вариант — поместить код обработки и вывода ошибки прямо в loadUsersFromFile():

function loadUsersFromFile($file) {
    // Файла не существует — ошибка
    if (!file_exists($file)) {
        die("Ошибка: файл $file не существуетn");
    }

    ....
}

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

Что же, давай улучшим код и переделаем функцию, чтобы она возвращала массив из 2 элементов: если все ок, то элемент success содержит true, а элемент result содержит массив пользователей. Если же произошла ошибка, то в success будет находиться false, а в элементе error текст ошибки.

function loadUsersFromFile($file) {
    // Файла не существует — ошибка
    if (!file_exists($file)) {
        return [
            'success'   =>  false,
            'error'     =>  "файл $file не существует"
        ];
    }

    .... загружаем информацию о пользователях ....

    return [
        'success'   =>  true,
        'result'    =>  $users
    ];
}

Конечно, мы должны поменять и код, который вызывает функцию:

....
// Загружаем список пользователей в массив
$loadResult = loadUsersFromFile($file); 

// можно еще писать if (!$loadResult['success'])
if ($loadResult['success'] === false) {  
    // Выводим текст ошибки
    die("Не удалось вывести список пользователей из-за ошибки: {$loadResult['error']}n");
}

$users = $loadResult['result'];
...

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

Выбрасываем исключение

В качестве решения проблемы были придуманы исключения. Если в функции произошла какая-то ошибка и она не может выполнить свою работу, то она выбрасывает исключение командой throw:

if (!file_exists($file)) {
    throw new Exception("Ошибка: файл $file не существует");
}

Исключение — это объект встроенного в PHP класса Exception (мануал по Exception) или его наследника. Объект исключения содержит подробности о причинах ошибки. Также, в PHP есть еще другие классы исключений, которые ты можешь использовать: http://php.net/manual/ru/spl.exceptions.php

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

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

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

Исключение по умолчанию (если оно не перехватывается) выходит из всех вызовов функций до самого верха и завершает программу, выводя сообщение об ошибке. Таким образом, если ты не перехватываешь исключения, то все равно увидишь причину ошибки (а если у тебя установлено расширение xdebug то еще и стектрейс — цепочку вызовов функций, внутри которых оно произошло). И тебе больше не надо писать if:

// Если тут произойдет исключение, оно само завершит программу
// потому у нас нет необходимости проверять результат
$users = loadUsersFromFile($file);

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

Вот простой пример:

function a() 
{
    b();
}

function b()
{
    throw new Exception("Some error happened");
}

// Функция a() вызывает b() которая выбрасывает исключение. Исключение выходит
// из функции b() наверх в функцию a(), выходит из нее и, оказавшись на верхнем 
// уровне, завершает программу
a();

// Эта строчка никогда не будет выполнена
echo "Survivedn"; 

В данном примере кода исключение мгновенно выйдет из всех вложенных вызовов функций и завершит программу. Это хорошо, так как если произошла какая-то неисправимая ошибка, выполнять программу дальше нельзя. Это называется принципом fail fast (статья на Хабре).

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

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

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

Перехватывать исключения в теории можно двумя способами: неструктурно и структурно. Неструктурно — это когда мы задаем обработчик исключений в начале программы:

set_exception_handler(function (Exception $exception) {
    // Функция будет вызвана при возникновении исключения        
});

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

Структурная обработка исключений — это когда мы ловим только исключения определенных типов в опредленном месте кода. Она реализуется с помощью try/catch:

try {
    // В try пишется код, в котором мы хотим перехватывать исключения
    $users = loadUsersFromFile(...);
    ....
} catch (LoadUsersException $e) {
    // В catch мы указываем, исключения каких классов хотим ловить.
    // В данном случае мы ловим исключения класса LoadUsersException и его 
    // наследников, то есть только те, которые выбрасывает наша функция
    // Блоков catch может быть несколько, для разных классов

    die("Ошибочка: {$e->getMessage()}n");
}

В PHP5.5 и выше добавлен блок finally. Команды из этого блока будут выполнены после любого из блоков (try или catch) — в случае если исключения не произойдет и в случае если оно произойдет.

Перехватывать абсолютно любые типы исключений — плохая идея, так как мы обычно хотим обрабатывать только определенные, «наши», типы ошибок и не знаем что делать с другими. Чтобы перехватывать только нужные нам исключения, надо сделать свой класс на основе встроенного в PHP Exception:

class LoadUsersException extends Exception { }

Выкидывать его в throw

throw new LoadUsersException("Файл $file не существует");

И ловить в catch только наши исключения:

catch (LoadUsersException $e) {
    .... обрабатываем ошибку ...
}

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

Поддержка исключений везде

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

// Если файла нет, функция просто вернет false, и программа продолжит выполняться
$content = file_get_contents('file.txt');
if ($content === false) {
    ... не удалось прочесть файл ...
}

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

Для этой проблемы есть решение. Можно установить общий обработчик ошибок (он вызывается при любой ошибке PHP, например обращении к несуществующей переменной или невозможности чтения файла), и в нем выкидывать исключение. Таким образом, любая ошибка или предупреждение приведут к выбросу исключения. Все это делается в несколько строчек с помощью встроенного в PHP класса ErrorException (мануал):

set_error_handler(function ($errno, $errstr, $errfile, $errline ) {
    // Не выбрасываем исключение если ошибка подавлена с 
    // помощью оператора @
    if (!error_reporting()) {
        return;
    }

    throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
});

Этот код превращает любые ошибки и предупреждения PHP в исключения. Некоторые современные фреймворки (Slim) включают в себя такой код.

Исключения и PDO

Чтобы расширение для работы с базами данных PDO использовало исключения (например при попытке выполнить неправильно написанный SQL запрос), надо установить соответствующий параметр (рекомендуется). Без него ты будешь должен после вызова каждой функции проверять результат с помощью if:

$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

Мануал: http://php.net/manual/ru/pdo.error-handling.php

Исключения и mysqli

Библиотека mysqli при ошибках не выбрасывает исключений и не генерирует предупреждений, а просто возвращает false (то есть молчит как партизан). Таким образом, после каждого действия ты должен проверять результат с помощью if, что видно в примерах кода в мануале: http://php.net/manual/ru/mysqli.query.php

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

Чтобы не писать ифы во всей программе, ты можешь сделать класс-обертку над mysqli. Или просто использовать PDO.

Так делать не надо

Не стоит ловить все исключения без разбора:

Лучше создать свой класс исключений и ловить только его.

Не надо скрывать информацию об ошибках. В 99% этот код, игнорирующий исключение, неправильный:

catch (Exception $e) {
    // ничего не делаем
}

Не надо располагать try/catch и throw на одном уровне — в этом случае проще написать if:

try {
    ... 
    throw new Exception(...);
    ...
} catch (Exception $e) {
    ...
}

Страница ошибки в веб-приложениях

По умолчанию при непойманном исключении PHP завершает скрипт. Если опция display_errors в php.ini равна 1, то PHP выводит подробности об исключении, а если она равна 0, то в браузере отображается просто белая страница. Также, PHP записывает информацию об исключении в лог ошибок сервера.

Очевидно что оба варианта не годятся для использования на продакшен («боевом») сервере: обычные пользователи не должны видеть непонятные надписи на английском о твоем приложении, и тем более не должны смотреть на пустую страницу и гадать в чем дело. А хакеры не должны видеть подробности об устройстве твоего приложения. Более того, PHP не выдает при ошибке HTTP код 500, который говорит роботам (вроде Гугла или Яндекса) что на странице ошибка и индексировать ее на надо. Разработчики PHP конечно выбрали неудачный способ поведения по умолчанию.

Потому на боевом сервере надо ставить display_errors в 0, а в приложении делать свою страницу ошибки.

Что будет, если просто не ловить исключение:

  • информация пишется в лог (ок)
  • на компьютере разработчика выводятся подробности (ок)
  • пользователь видит белую страницу или подробности ошибки (плохо)
  • отдается HTTP код 200 (плохо)

Как надо обрабатывать исключения:

  • записать информацию в лог
  • показать пользователю заглушку («сайт временно недоступен, вот контакты администратора»)
  • на заглушке выставить HTTP код ответа 503 для роботов
  • на компьютере разработчика (при display_errors = 1) можно показать подробности и стектрейс

Для реализации страницы ошибки можно либо сделать try/catch на уровне FrontController, либо установить свой обработчик исключений через set_exception_handler. Не забудь записать информацию в лог с помощью error_log($e->__toString()) — иначе ты не узнаешь об ошибках которые происходят у пользователей твоего приложения.

Если ты используешь фреймворк, возможно, в нем все это уже реализовано. Современные фрейморки, такие как Slim, Yii 2, Symfony 2, выводят заглушку при непойманном исключении. Старые — не выводят.

Ссылки

Механизм исключений существует и в других языках в таком же виде: Java, C++, Ruby, Javascript и многих других. Статья в вики:

https://ru.wikipedia.org/wiki/%D0%9E%D0%B1%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%BA%D0%B0_%D0%B8%D1%81%D0%BA%D0%BB%D1%8E%D1%87%D0%B5%D0%BD%D0%B8%D0%B9 (написано не очень понятно)

Также, про исключения можно почитать в книгах:

  • Мэтт Зандстра «PHP: Объекты, шаблоны, методики программирования»
  • Джордж Шлосснейгл «Профессиональное программирование на PHP»

PHP is the language used to build websites on the internet for over ten years. Although, there are a lot of people who think that it’s time to move into something else, PHP is a dynamic programming language, which means that it can be adapted to the current needs. And the PHP Core team has been excellent in bringing out new features that make PHP an attractive language in this time and age.

The flexibility in the PHP language makes it easy to handle things like exceptions in code, which are the out of the ordinary scenarios that can occur. They can be caused by some unexpected input, a bug, or some other problem. PHP 8 is a new version of this language that was released on 26 November 2020. The new version has been adapted to be more secure and handle exceptions better than the previous versions.

Potential exceptions/errors are enclosed inside a try block if exception is encountered, will be thrown to catch or finally block. PHP usually handles exceptions in a separate catch block for each different type of exception.

In this post, you can gain knowledge about what exactly is exception handling, and how it works.

Below are the topics that shall be covered in this blog:

  1. When, Where, and How to use Exceptions and Errors in PHP?
  2. Error Class

  3. Exception Class

  4. Custom Exception

  5. Multiple Exception

  6. Global Exception Handler

  7. Non-Capturing Catches

#1 When, Where, and How to use Exceptions and Errors in PHP?

PHP 7 introduced the new Throwable interface to unite the exception branches Exception and Error. The entire PHP exception hierarchy is as follows:

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

To catch both exceptions and errors in PHP 8, add a catch block for Exception after catching Throwable first.

try 
{ 
    // Code that may throw an Exception or Error. 
} catch (Throwable $t) 
{ 
   // Executed only in PHP 7 and more
}

#2 Error Class

Error Class is the base class for all internal PHP errors. Errors can be caught in  try/catch block as explained above. Few errors will throw a specific subclass of Error such as Parse Error, Type Error, and so on.

Here are the list of various types of errors (we have covered only the most common ones):

  1. Parse/Syntax Error
  2. Type Error

  3. Arithmetic Error
  4. Assertion Error

  5. Value Error

a. Parse/Syntax Error

A syntax/parse error in the code while compilation, a Parse error is thrown. If a code contains an error, the PHP parser cannot interpret the code and it stops working.

Let’s look into a simple example for understanding Parse error.

Code:

<?php
    $x = "Exception";
    y = "Handling";
    echo $x . ' ' . y;
?>

Output:

syntax error, unexpected '=' in line 3

b. Type Error

When data type mismatch happens in PHP while doing an operation, a Type error is thrown. There are three scenarios where this type of error is thrown:

  • Invalid number of arguments passed to a built-in function.
  • Value returned from a function doesn’t match the declared function return type.
  • Argument type passed to a function doesn’t match the declared parameter type.

Let’s look into a simple example for understanding Type error.

Code:

<?php
function add(int $x, int $y)
{
    return $x + $y;
}
try {
    $value = add('Type', 10);
}
catch (TypeError $e) {
    echo $e->getMessage(), "n";
}
?>

Output:

Argument 1 passed to add() must be of the type integer, string given.

c. Arithmetic Error

Occurrence of error while performing a mathematical operation, bit shifting by a negative number or calling an intdiv() function, the Arithmetic error is thrown.

Example With Division Operator:

<?php
try {
    intdiv(PHP_INT_MIN, -1);
}
catch (ArithmeticError $e) {
    echo $e->getMessage();
}
?>

Output:

Division of PHP_INT_MIN by -1 is not an integer

Example With Modulo Operator:

<?php
try {
    $x = 4;
    $y = 0;
    $result = $x%$y;
}
catch (DivisionByZeroError $e) {
   echo $e->getMessage();
}
?>

Output:

Modulo by zero error

Example With Division Operator Which Returns INF:

<?php
try {
    $x      = 4;
    $y      = 0;
    $result = $x / $y;
}
catch (DivisionByZeroError $e) {
    echo $e->getMessage();
}
?>

Output:

INF

Explanation:

There is a very minute difference in the above two examples. The first one contains the Modulo operator and the second one has the  Division operator. If any variable divided by zero will return an error, Division by zero error. When any variable divided by zero with modulo operator returns Modulo by zero error and the variable divided by zero with the division operator also returns anyone the following- INF, NAN, -INF

d. Assertion Error

When an assert() call fails or let’s say when the condition inside the assert() call doesn’t meet, the Assertion error is thrown. String description emits E_DEPRECATED message from PHP 7.2 version. The Assertion Error thrown by assert() will be sent to catch block only if assert.exception=on is enabled in php.ini.

Let’s look into a simple example for understanding assertion error.

Code:

<?php
try {
    $x      = 1;
    $y      = 2;
    $result = assert($x === $y);
    if (!$result) {
        throw new DivisionByZeroError('Assertion error');
    }
}
catch (AssertionError $e) {
    echo $e->getMessage();
}
?>

Output:

Assertion error

e. Value Error

When the type of the argument is correct and the value of it is incorrect, a value error is thrown. These type of errors occurs when:

  • Passing a negative value when the function expects a positive value.
  • Passing an empty string or array when function expects a non-empty string/array.

Let’s look into a simple examples for understanding value error.

Code:

<?php
    $x = strpos("u", "austin", 24);
    var_dump($x);
?>

Output:

[Mon Feb 22 20:59:04 2021] 
PHP Warning:  strpos(): Offset not contained in string in /home/ubuntu/value_error.php on line 2

Code:

<?php
    $x = array_rand(array(), 0);
    var_dump($x);
?>
[Mon Feb 22 21:04:14 2021] PHP Warning:  array_rand(): Array is empty in /home/ubuntu/index.php on line 2

#3 Exception Class

Exception Class occurs when a specified/exceptional error condition changes the normal flow of the code execution.

Exception handling comprises five components i.e, try block, exception, throw, catch block, and finally block.

Let’s look into a simple example for understanding the above-mentioned components

Code:

<?php
function add($x,$y) {
    if (is_numeric($x) == False) {
        throw new Exception('Num1  is not a number');
    }
    if (is_numeric($y) == False) {
        throw new RuntimeException('Num2 is not a number');
    }
    return $x + $y;
}

try {
    echo add(5,10). "n";
    echo add(5,k). "n";
} 

catch (Exception $e) {
    echo 'Exception caught: ', $e->getMessage(), "n";
} 

finally {
    echo "Finally Block.n";
}

// Continue execution
echo "Hello Worldn";
?>

Output:

15
Exception caught: Num2 is not a number
Finally Block.
Hello World

Explanation:

Our example is about adding two numbers and we assumed that we might get non-numeric value as input which would raise an error.

  • We created a function called addition with Exception for non-numeric values and If encountered with the exception, will throw it with the exception message.

  • We called the addition function inside a Try block so that non-numeric value error won’t affect/stop the whole execution. All potential exceptions should be enclosed inside a try block.

  • The Catch block will receive any exceptions thrown from the try block and execute the code inside the block. In our case will print the error message ‘Caught exception: Num2 is not a number’.

  • The Finally block will be executed irrespective of whether we received exception or not.

#4 Custom Exception

We use custom exception to make it clear what is being caught in the catch block and to understand the exception in a better way. The custom exception class inherits properties from the PHP exception’s class where you can add your custom functions too. To easily understand the exceptions we can use custom exceptions and can log it for the future use.

If you just want to capture a message, you can do it at follows:

try {
    throw new Exception("This is an error message");
}
catch(Exception $e) {
    print $e->getMessage();
}

If you want to capture specific error messages which could be easy to understand you can use:

try {
    throw new MyException("Error message");
}
catch(MyException $e) {
    print "Exception caught: ".$e->getMessage();
}
catch(Exception $e) {
    print "Error: ".$e->getMessage();
}

Code:

<?php

class customStringException extends Exception
{
    public function myerrorMessage()
    {
        //error message
        $errorMsg = 'Error on line ' . $this->getLine() . ': <b>' . $this->getMessage() . '</b> is not a String';
        return $errorMsg;
    }
}

class customNumericException extends Exception
{
    public function myerrorMessage()
    {
        //error message
        $errorMsg = 'Error on line ' . $this->getLine() . ': <b>' . $this->getMessage() . '</b> is not an Integer';
        return $errorMsg;
    }
}

function typeCheck($name, $age)
{
    if (!is_string($name)) {
        throw new customStringException($name);
    }
    if (!is_numeric($age)) {
        throw new customNumericException($age);
    } else {
        echo $name . " is of age " . $age;
    }
}

try {
    echo typeCheck("Sara", 25) . "n";
    echo typeCheck(5, 10) . "n";
}
catch (customStringException $e) {
    echo $e->myerrorMessage();
}
catch (customNumericException $e) {
    echo $e->myerrorMessage();
}

?>

Output:

Sara is of age 25
Error on line 21: 5 is not a String

Explanation:

The above example is on a type check, we have two variables name and age . Let’s assume $name is of type string and $age is of type integer and we assumed that we might get any type of value as input which would raise an error.

  • We created a function called typeCheck to check the type of the variable with exception. If the condition fails it will throw an exception with an Exception message that we have customized.

  • We created a class customStringException to create a custom exception handler with a function called errorMessage which would be called when an exception occurs.

  • Here we called the typeCheck function inside a try block so that if any error is encountered it could be caught in the catch block.

#5 Multiple Exception

You can also handle multiple exception in a single catch block using the pipe ‘|’ symbol like this:

try {
    $error = "Foo / Bar / Baz Exception"; throw new MyBazException($error); 
} 
catch(MyFooException | MyBarException | MyBazException $e) { 
    //Do something here 
}

#6 Global Exception Handler

In Global Exception Handler, it sets the default exception handler if an exception is not caught within a try/catch block. If no other block is invoked the set_exception_handler function can set a function which will be called in the place of catch. Execution will stop after the exception_handler is called.

set_exception_handler Syntax:

set_exception_handler ( callable $exception_handler ) : callable

<?php

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

set_exception_handler('exception_handler');

throw new Exception('Uncaught Exception');

echo "Not Executedn";
?>

#7 Non-Capturing Catches

Before PHP version 8, if you wanna catch an exception you will need to store it in a variable irrespective of its usage. You usually must specify the type whenever you use a catch exception. With this Non-Capturing Catch exception, you can ignore the variable.

Example:

try {
    // Something goes wrong
} 
catch (MyException $exception) {
    Log::error("Something went wrong");
}

You can put it this way in PHP 8:

try {
    // Something goes wrong
} 
catch (MyException) {
    Log::error("Something went wrong");
}

Summary:

Here we have explained the basic usage of exceptions and how to implement it in detail.You can quickly track the errors and fix the exceptions that have been thrown in your code. I hope this blog might be useful for you to learn what exception is and the correct usage of it.

If you would like to monitor your PHP code, you can try Atatus here.

Генерация исключений

Последнее обновление: 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:localhosthello.php:17 Stack trace: #0 D:localhosthello.php(26): Person->__construct('Tom', -105) #1 {main} thrown in D:localhosthello.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

Russian (Pусский) translation by Anna Goorikova (you can also view the original English article)

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

Что такое исключение?

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

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

С другой стороны, исключения — это что-то, что умышленно вызывает код, и ожидается, что он будет пойман в какой-то момент вашего приложения. Таким образом, мы можем сказать, что исключения восстанавливаются, а не определенные ошибки, которые не подлежат восстановлению. Если исключение, которое выбрасывается, попадает где-то в ваше приложение, выполнение программы продолжается с момента, когда исключение было поймано. А исключение, которое не попадает нигде в ваше приложение, приводит к ошибке, которое останавливает выполнение программы.

Поток управления обработкой исключений

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

Exception handling in a try catch finally blockException handling in a try catch finally blockException handling in a try catch finally block

Исключения могут быть выброшены и пойманы с помощью блоков try и catch. Вы несете ответственность за выброс исключений, если что-то произойдет, чего не ожидается. Давайте быстро рассмотрим основной поток обработки исключений, как показано в следующем псевдокоде.

1
// code before the try-catch block

2

3
try {
4
  // code

5

6
  // if something is not as expected

7
      // throw exception using the "throw" keyword

8

9
  // code, it won't be executed if the above exception is thrown

10
} catch (Exception $e) {
11
  // exception is raised and it'll be handled here

12
  // $e->getMessage() contains the error message

13
}
14

15
// code after the try-catch block, will always be executed

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

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

Выброс исключения

Исключение может быть вызвано функцией, которую вы вызываете, или вы можете использовать ключевое слово throw для выбрасывания исключения вручную. Например, вы можете проверить некоторый ввод перед выполнением любой операции и выбросить исключение, если данные недействительны.

Здесь важно отметить, что если вы выбросите исключение, но вы не определили блок catch, который должен обрабатывать это исключение, это приведет к фатальной ошибке. Поэтому вам нужно убедиться, что вы всегда определяете блок catch, если вы бросаете исключения в своем приложении.

Когда исключение попадает в блок catch, объект Exception содержит сообщение об ошибке, которое было выбрано с использованием ключевого слова throw. Переменная $e в приведенном выше примере является экземпляром класса Exception, поэтому она имеет доступ ко всем методам этого класса. В этом блоке вы должны определить свою собственную логику обработки исключений — что именно вы хотите сделать с ошибкой, которую вы поймаете.

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

Пример из реального мира

В этом разделе мы построим реальный пример для демонстрации обработки исключений в PHP.

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

1
<?php
2
try {
3
    // init bootstrapping phase

4

5
    $config_file_path = "config.php";
6

7
    if (!file_exists($config_file_path))
8
    {
9
      throw new Exception("Configuration file not found.");
10
    }
11
 
12
    // continue execution of the bootstrapping phase

13
} catch (Exception $e) {
14
    echo $e->getMessage();
15
    die();
16
}
17
?>

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

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

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

Как создавать пользовательские исключения

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

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

Перейдем к предыдущему примеру, как показано в следующем фрагменте.

1
<?php
2
class ConfigFileNotFoundException extends Exception {}
3

4
try {
5
    // init bootstrapping phase

6

7
    $config_file_path = "config.php";
8

9
    if (!file_exists($config_file_path))
10
    {
11
      throw new ConfigFileNotFoundException("Configuration file not found.");
12
    }
13
 
14
    // continue execution of the bootstrapping phase

15
} catch (ConfigFileNotFoundException $e) {
16
    echo "ConfigFileNotFoundException: ".$e->getMessage();
17
    // other additional actions that you want to carry out for this exception

18
    die();
19
} catch (Exception $e) {
20
    echo $e->getMessage();
21
    die();
22
}
23
?>

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

Затем мы использовали ключевое слово throw для исключения исключений ConfigFileNotFoundException в случае, если файл config.php не существует. Однако важное различие находится в блоке catch. Как вы можете видеть, мы определили два блока catch, и каждый блок используется для обнаружения различного типа исключения.

Первый получает исключения типа ConfigFileNotFoundException. Итак, если генерируемое исключение относится к типу ConfigFileNotFoundException, этот блок будет выполнен. Если тип исключения не соответствует какому-либо конкретному блоку catch, он будет соответствовать последнему, который должен поймать все генерические сообщения об исключениях.

Блок Finally

В этом разделе мы рассмотрим, как вы можете использовать ключевое слово finally вместе с блоками try и catch. Иногда вы хотите выполнить часть кода независимо от того, было ли исключено исключение. Вот где вы можете использовать блок finally, поскольку код, который вы размещаете в блоке finally, всегда будет выполняться после выполнения блоков try и catch независимо от того, было ли выбрано исключение.

Попробуем понять это, используя следующий пример.

1
try {
2
  // code

3

4
  // if something is not as expected

5
      // throw exception using the "throw" keyword

6

7
  // code, it won't be executed if the above exception is thrown

8
} catch (Exception $e) {
9
  // exception is raised and it'll be handled here

10
  // $e->getMessage() contains the error message

11
} finally {
12
  // code, it'll always be executed

13
}

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

Типичные прецеденты, которые мы могли бы придумать для использования блока finally, обычно связаны с очисткой ресурсов. Например, если вы открыли соединение с базой данных или файл на диске в блоке try, вы можете выполнять задачи очистки, такие как закрытие соединения в блоке finally, так как это гарантировано.

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

Заключение

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

Понравилась статья? Поделить с друзьями:
  • Throw new error library dir does not exist libdir
  • Throw new error jquery requires a window with a document
  • Throw new error jest
  • Throw new error in promise
  • Throw new error cheerio load expects a string