#10. 5 php Исключения. Базовый синтаксис try...catch. Инструкция throw. Раскрутка стека вызова функций. Глобальный обработчик

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

Все исключения, генерируемые внутренними PHP-функциями, наследуются от стандартного класса Error. Например, при выполнении

<?php
echo 1/0;

будет "выброшено" исключение встроенного типа DivisionByZeroError.

А все новые пользовательские типы исключений должны наследоваться от базового класса Exception :

<?php
class myException extends Exception
{
}

Оба класса, Error и Exception, реализуют интерфейс Throwable.

Базовый синтаксис. Конструкция try..catch

Код, который проверяется на наличие ошибок, нарушающих логику алгоритма, помещается в блок try. Блок catch отвечает за обработку исключений, совместимых по присваиванию с его аргументом (то есть объектов того же класса или наследников). Любые исключения можно поймать, указав Throwable.

Пример 1. Обработка автоматически генерируемого PHP исключения

<?php
for ($i=0; $i<100; $i++) {
    try {
        echo 1/mt_rand(-5, 5), "<BR>";
    } catch (Throwable $e) {
        echo $e->getMessage(), "<BR>";
    }
}

В приведенном демонстрационном примере 1 в цикле for выполняется деление на случайное число от -5 до 5. Соответствующая инструкция помещается в блок try. В случае деления на ноль автоматически генерируется исключение и передается в блок catch, в котором с помощью метода getMessage(), предоставляемого интерфейсом Throwable, выводится стандартное сообщение об ошибке данного типа. При этом программа не прерывается, и после обработки исключения в блоке catch выполняется переход к следующей итерации цикла.

Несколько блоков catch

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

<?php
try {
    //какой-то код
} catch (myException $e) {
    echo $e->getMessage();
} catch (Exception $e)  {
    error_log($e->getMessage());
}

Если исключение не может быть обработано первым блоком catch, то оно будет передано дальше: во второй и т.д.

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

<?php
try {
    //какой-то код
} catch (myException |  myOtherException) {
    //обработка выброшенного исключения
}

Инструкция throw

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

Пример 2. Использование throw для генерации пользовательского исключения

    
class KeyFieldMissingException extends Exception
{
    public function __(string $name)
    {
        parent::__construct("Поле $name обязательно для заполнения");
    }
}

function validateUserData()
{
    $keyFields = array("Имя", "Номер телефона");
    
    foreach ($keyFields as $field) {
        if ($_REQUEST[$field] == "") {
            throw new KeyFieldMissingException($field);
        }
    }
}

Раскрутка стека вызовов функций

В приведенном выше примере 2 отсутствует конструкция try..catch. Это допустимо, если функция validateUserData(), выбрасывающая исключение, вызывается из блока try другой функции или основной программы. Например,

<?php
function saveUserData()
{
    echo "Данные успешно сохранены.";
}

try {
    validateUserData();
    saveUserData();
} catch (Exception $ex) {
    echo "Не удалось сохранить данные: " . $ex->getMessage();
}

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

Не удалось сохранить данные: Поле Номер телефона обязательно для заполнения

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

Замечание

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

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

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

Для неперехваченных ни в одном блоке catch исключений можно установить глобальный обработчик с помощью функции set_exception_handler():

<?php
function myExceptionHandler(Throwable $e) 
{
    echo "Неперехваченное исключение: " . $e->getMessage();
}

set_exception_handler('myExceptionHandler');

echo 1/0;

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

Конструкция try..finally

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

<?php
try {
    echo "Внешний блок try<BR>";
    try {
        echo "Вложенный блок try<BR>";
        throw new Exception();
    } catch (Exception $e) {
        echo "Вложенный блок catch. Пробрасываем исключение дальше<BR>";
        throw $e;
   } finally {
        echo "Блок finally<BR>";
   }
} catch (exception $e) {
    echo "Внешний блок catch<BR>";
}

Результат:

Внешний блок try
Вложенный блок try
Вложенный блок catch. Пробрасываем исключение дальше
Блок finally
Внешний блок catch