Исключения в PHP 7


С PHP 7 к нам приходит новая эпоха обработки исключений. Впервые PHP стал выкидывать исключения для фатальных и восстанавливаемых фатальных ошибок. Теперь мы можем их обрабатывать, как захотим. Больше никаких @, include, file_exists и прочей не логичности в коде.

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

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

Throwable (интерфейс)
├── Exception (реализует Throwable)
│   ├── LogicException (расширяет Exception)
│   │   ├── BadFunctionCallException (расширяет LogicException)
│   │   │   └── BadMethodCallException (расширяет BadFunctionCallException)
│   │   ├── DomainException (расширяет LogicException)
│   │   ├── InvalidArgumentException (расширяет LogicException)
│   │   ├── LengthException (расширяет LogicException)
│   │   └── OutOfRangeException (расширяет LogicException)
│   └── RuntimeException (расширяет Exception)
│       ├── OutOfBoundsException (расширяет RuntimeException)
│       ├── OverflowException (расширяет RuntimeException)
│       ├── RangeException (расширяет RuntimeException)
│       ├── UnderflowException (расширяет RuntimeException)
│       └── UnexpectedValueException (расширяет RuntimeException)
└── Error (реализует Throwable)
    ├── AssertionError (расширяет Error)
    ├── ParseError (расширяет Error)
    └── TypeError (расширяет Error)

Новые исключения Error

Как вы уже смогли заметить из таблицы иерархий, в PHP 7 добавились новые исключения для отлавливания фатальных (E_ERROR) и восстанавливаемых ошибок (E_RECOVERABLE_ERROR).
Но к сожалению, определенные ошибки, например «out of memory», по прежнему приведут к остановке, как их вообще обрабатывать? 😀

Error

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

AssertionError

В PHP 7, есть усовершенствование утверждений, используя assert() функцию, с добавлением нулевой стоимостью утверждений, и выбрасыванием исключений. Для включения данной возможности, нужно установить параметр assert.exception = 1 в php.ini (или с использованием ini_set()).

ini_set('zend.assertions', 1);
ini_set('assert.exception', 1);

$a = 1;
assert($a === 0);

// Result
Fatal error: Uncaught AssertionError: assert($a === 0)

ParseError

Благодаря выкидыванию исключений ParseError при ошибке включения файла, теперь можно обработать поведение:

<?php
try 
{
    include 'parse-error.php';
} 
catch (ParseError $e) 
{
    // parse error!
}

TypeError

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

function add(int $left, int $right)
{
    return $left + $right;
}

try {
    $value = add('left', 'right');
} catch (TypeError $e) {
    echo $e->getMessage(), "\n";
}

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

Отлавливаем фатальные ошибки в PHP7

Еще одно важное изменение PHP7 в отлавливании фатальных ошибок. Раньше, они ловились и обрабатывались с использованием set_error_handler(). Теперь, в PHP 7, они выкидывают Error исключения, и если, исключения не будут обработаны, то выбрасывается реальная фатальная ошибка, которая больше не будет доступна в set_error_handler().

Это было сделано для обратной совместимости и для работы в PHP 5.x и 7, вы должны использовать bothset_error_handler() и try... catch.

Использование Throwable

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

<?php
class MyException implements Throwable { }
?>

Fatal error: Class MyException cannot implement interface Throwable, extend Exception or Error instead

Но это еще не вся история, есть лайфхак, который не рекомендуется к использованию. Вы можете расширить интерфейс Throwable  — а уже потом, реализовать свои исключения типов Error или Exception используя свой расширенный интерфейс.

<?php
namespace MyLibrary;

interface MyPackageException extends Throwable 
{
  public function someMethod();
 
  public function someOtherMethod();
}

class MyException extends Exception implements MyLibraryMyPackageException 
{

}
?>

Fatal error: Class MyLibraryMyException contains 2 abstract methods and must therefore be declared
abstract or implement the remaining methods (MyLibraryMyPackageException::someMethod, 
MyLibraryMyPackageException::someOtherMethod)

Заключение

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

https://wiki.php.net/rfc/throwable-interface

Информация по теме