Throwable exception и ошибки в php7

Original author: Aaron Piotrowski
  • Translation
В прошлом, обрабатывать фатальные ошибки было практически невозможно. Обработчик, установленный set_error_handler вызван не будет, скрипт просто будет завершен.
В PHP 7 при возникновении фатальных ошибок (E_ERROR) и фатальных ошибок с возможностью обработки (E_RECOVERABLE_ERROR) будет выброшен exception, а не произойдет завершение скрипта. Но определенные ошибки, например «out of memory», по прежнему приведут к остановке. Не перехваченные ошибки в PHP 7, будут «фатальны», так же как и в php 5.*.
Обратите внимание, что другие виды ошибок, такие как warinng и notice остаются без изменения в php 7.

Исключения выброшенные из E_ERROR и E_RECOVERABLE_ERROR не наследуются от Exception. Это разделение было сделано, чтобы предотвратить обработку этих ошибок кодом, написанным под 5.*. Исключения для фатальных ошибок теперь являются экземпляром нового класса: Error. Как и любые другие исключения, Error может отловлен, обработан и выполнен finally блок.
Throwable

Оба класса, и Error и Exception реализуют новый интерфейс Throwable.
Новая иерархия исключения состоит в следующем:
interface Throwable
    |- Exception implements Throwable
        |- ...
    |- Error implements Throwable
        |- TypeError extends Error
        |- ParseError extends Error
        |- AssertionError extends Error


Если Throwable определить в коде PHP 7, то выглядит это так:
interface Throwable
{
    public function getMessage(): string;
    public function getCode(): int;
    public function getFile(): string;
    public function getLine(): int;
    public function getTrace(): array;
    public function getTraceAsString(): string;
    public function getPrevious(): Throwable;
    public function __toString(): string;
}

Этот интерфейс должен быть знаком. Методы Throwable практически идентичны методам Exception. Разница лишь в том, что Throwable::getPrevious() может вернуть любой экземпляр Throwable, а не просто Exception. Конструкторы Exception и Error принимают любой экземпляр Throwable как предыдущее исключение.
Throwable может быть использован в блоке try/catch для отлова и Exception и Error (и любых других возможных в будущем исключений). Помните, что хорошей практикой является «ловля» исключений определенным классом исключений и обработка каждого типа отдельно. Но и иногда требуется отлавливать любое исключение. В PHP 7 try/catch блок для всех исключений должен использовать Throwable вместо Exception.
try {
    // Code that may throw an Exception or Error.
} catch (Throwable $t) {
    // Handle exception
}


Пользовательские классы не могут реализовывать Throwable. Это было сделано для предсказуемости: только экземпляры Exception или Error могут быть брошены. Кроме того, исключения содержат информацию о том, где объект был создан в stack trace. В пользовательских классах нет необходимых параметров, для хранения этой информации.

Error

Практически все ошибки (E_ERROR, E_RECOVERABLE_ERROR) в PHP 5.x, в PHP 7 выбрасывается экземпляром Error. Как и любые другие исключения, Error может быть пойман используя try/catch блок.
try {
    $undefined->method(); // Throws an Error object in PHP 7.
} catch (Error $e) {
    // Handle error
}


Большинство ошибок, которые были «фатальны» в PHP 5.x в PHP 7 буду выбрасывать простые Error объекты, но некоторые будут выбрасывать объекты подклассов: TypeError, ParseError и AssertionError.

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


ParseError

ParseError выбрасывается, когда подключаемый (путем include/require) файл или код в eval содержит ошибки синтаксиса.
try {
    require 'file-with-parse-error.php';
} catch (ParseError $e) {
    echo $e->getMessage(), "\n";
}


AssertionError

Когда условие, заданное методом assert() не выполняется, выбрасывается AssertionError:
ini_set('zend.assertions', 1);
ini_set('assert.exception', 1);

$test = 1;

assert($test === 0);

Fatal error: Uncaught AssertionError: assert($test === 0)

Метод assert() выполняется и выбрасывается AssertionError только, если они включены в настройках: zend.assertions = 1 и assert.exception = 1.

Использование Error в своём коде

Мы можем использовать класс Error, а также расширить Error, создав собственную иерархию класса Error. Это порождает вопрос: какие исключение должен выбрасывать Exception, а какие Error?
Error должен использоваться для указания проблем в коде, требующих внимания программиста (такие как неправильный тип входящих данных и синтаксические ошибки). Exception должен использоваться, когда исключение может «безопасно» обработаться, и выполнение программы может продолжиться.
Поскольку, объекты Error не могут быть обработаны во время выполнения программы, «ловля» Error должна быть редкостью. В целом, Error должны быть пойманы только для логирования их, необходимой «чистки данных», и отображения ошибки для пользователя.

Ловим исключения и в PHP 5.x и в PHP 7

Чтобы поймать исключения и в php 5.x и в php 7, используя один код, используем несколько блоков catch, ловим Throwable первым, затем Exception. После того, как поддержка PHP 5.x не потребуется, можно просто удалить блок ловли Exception.
try {
    // Code that may throw an Exception or Error.
} catch (Throwable $t) {
    // Executed only in PHP 7, will not match in PHP 5.x
} catch (Exception $e) {
    // Executed only in PHP 5.x, will not be reached in PHP 7
}
  • +15
  • 24.3k
  • 8
Share post

Comments 8

    0
    PHP7 становится очень похож на Java. Например, если в последнем фрагменте кода убрать $ перед переменными, то будет код на Java (который, кстати, не скомпилируется из-за exception has already been caught)

      0
      Если так судить, то последний блок это c#. Только $ убрать
        0
        Разве C# и Java не похожи? :) Конечно, можно долго спорить и том, что именно можно называть «похожестью», но так уж сложилось, что многие конструкции почти во всех ООП языках выглядят «похожим» образом.
          0
          А в C# оно скомпилируется?
          0
          По секрету: в java в именах переменных разрешено использовать $. И юникод.
            0
            Можно, но мало кто так делает. Плюс в Java $ есть при обозначении inner class'ов
          +4
          Хорошо что они отошли от путающих BaseException, его наследника EngineException и пришли к Throwable и Error. Главное чтобы ещё сообщество ими правильно воспользовалось и тогда работать с PHP станет гораздо удобнее.
            –1
            Ошибки можно было ловить начиная с 5.2, используя register_shutdown_function + error_get_last.

            Only users with full accounts can post comments. Log in, please.