Механизм обработки исключений или просто "исключения" (exceptions) — это технология, позволяющая писать код восстановления после серьезной ошибки в удобном для программиста виде. С применением исключений перехват и обработка ошибок, наиболее слабая часть в большинстве программных систем, значительно упрощается.
Концепция исключений базируется на общей идее объектно-ориентированного программирования: данные должны обрабатываться в том участке программы, который имеет максимум сведений о том, как это делать. Если в некотором месте еще не до конца известно, какое именно преобразование должно быть выполнено, лучше отложить работу "на потом". С использованием исключений код обработки ошибки явно отделяется от кода, в котором ошибка может возникнуть.
Исключения также позволяют удобно передавать информацию о возникшей ошибке вниз по дереву (стеку) вызовов функций. Таким образом, код восстановления может находиться даже не в текущей процедуре, а в той, что ее вызывает.
БАЗОВЫЙ СИНТАКСИС
Исключение — это некоторое сообщение об ошибке вида "серьезная". При своей генерации оно автоматически передается в участок программы, который лучше всего "осведомлен", что же следует предпринять в данной конкретной ситуации. Этот участок называется обработчиком исключения.
Любое исключение в программе представляет собой объект некоторого класса, создаваемый, как обычно, оператором new. Этот объект может содержать различную информацию, например, текст диагностического сообщения, а также номер строки и имя файла, в которых произошла генерация исключения. Допустимо добавлять и любые другие параметры.
Прежде чем идти дальше, давайте рассмотрим простейший пример вызова обработчика (листинг 3.1). Заодно получим представление о синтаксисе исключений.
Листинг.3.1. Файл simple.php
<?php ## Простой пример использования исключений.
echo "Начало программы.<br>";
try {
// Код, в котором перехватываются исключения.
echo "Все, что имеет начало...<br>";
// Генерируем ("выбрасываем") исключение.
throw new Exception("Hello!");
echo "...имеет и конец.<br>";
} catch (Exception $e) {
// Код обработчика.
echo " Исключение: {$e->getMessage()}<br>";
}
echo "Конец программы.<br>";
?>
В листинге 3.1 приведен пример базового синтаксиса конструкции try...catch, применяемой для работы с исключениями.
Рассмотрим эту инструкцию подробнее:
● Код обработчика исключения помещается в блок инструкции catch (в переводе с английского — "ловить").
● Блок try (в переводе с английского — "попытаться") используется для того, чтобы указать в программе область перехвата. Любые исключения, сгенерированные внутри нее (и только они), будут переданы соответствующему обработчику.
● Инструкция throw используется для генерации исключения. Генерацию также называют возбуждением или даже выбрасыванием (или "вбрасыванием") исключения (от англ. throw — бросать). Как было замечено ранее, любое исключение представляет собой обычный объект РНР, который мы и создаем в операторе new.
● Обратите внимание на аргумент блока catch. В нем указано, в какую переменную должен быть записан "пойманный" объект-исключение перед запуском кода обработчика. Также обязательно задается тип исключения — имя класса. Обработчик будет вызван только для тех объектов-исключений, которые совместимы с указанным типом (например, для объектов данного типа).
Работа инструкции try...catch заключается в том, что одна часть программы "бросает" (throw) исключение, а другая — его "ловит" (catch).
ИНСТРУКЦИЯ throw
Инструкция throw не просто генерирует объект-исключение и передает его обработчику блока catch. Она также немедленно завершает работу текущего try-блока. Именно поэтому результат работы сценария из листинга 3.1 выглядит так:
Начало программы.
Все, что имеет начало...
Исключение: Hello!
Конец программы.
Как видите, за счет особенности инструкции throw наша программа подвергает серьезному скепсису тезис "Все, что имеет начало, имеет и конец" — она просто не выводит окончание фразы.
В этом отношении инструкция throw очень похожа на инструкции return, break и continue: они тоже приводят к немедленному завершению работы текущей функции или итерации цикла.
РАСКРУТКА СТЕКА
Самой важной и полезной особенностью инструкции throw является то, что ее можно использовать не только непосредственно в try-блоке, но и внутри любой функции, которая оттуда вызывается. При этом производится выход не только из функции, содержащей throw, но также и из всех промежуточных процедур. Пример — в листинге 3.2.
Листинг3.2.Файл stack.php
<?php ## Инструкция try во вложенных функциях.
echo "Начало программы.<br>";
try {
echo "Начало try-блока.<br>";
outer();
echo "Конец try-блока.<br>";
} catch (Exception $e) {
echo " Исключение: {$e->getMessage()}<br>";
}
echo "Конец программы.<br>";
function outer() {
echo "Вошли в функцию ".__METHOD__."<br>";
inner();
echo "Вышли из функции ".__METHOD__."<br>";
}
function inner() {
echo "Вошли в функцию ".__METHOD__."<br>";
throw new Exception("Hello!");
echo "Вышли из функции ".__METHOD__."<br>";
}
?>
Результат работы данного кода выглядит так:
Начало программы.
Начало try-блока.
Вошли в функцию outer
Вошли в функцию inner
Исключение: Hello!
Конец программы.
Мы убеждаемся, что ни один из операторов echo, вызываемых после инструкции throw, не "сработал". По сути, программа даже не дошла до них: управление было мгновенно передано в catch-блок, а после этого — в следующую за try...catch строку программы.
Данное поведение инструкции throw называют раскруткой стека вызовов функций, потому что объект-исключение последовательно передается из одной функции в другую, каждый раз приводя к ее завершению — как бы "отматывает" стек.
Можно заметить, что инструкция throw очень похожа на команду return, однако она вызывает "вылет" потока исполнения не только из текущей функции, но также и из тех, которые ее вызвали (до ближайшего соответствующего catch-блока).
ИСКЛЮЧЕНИЯ И ДЕСТРУКТОРЫ
Деструктор любого объекта вызывается всякий раз, когда последняя ссылка на этот объект оказывается потерянной, например, программа выходит за границу области видимости переменной. Применительно к механизму обработки исключений это дает мощный инструмент — корректное уничтожение всех объектов, созданных до вызова throw. Листинг 3.3 иллюстрирует ситуацию.
Листинг 3.3. Файл destr.php
<?php ## Деструкторы и исключения.
// Класс, комментирующий операции со своим объектом.
class Orator {
private $name;
function __construct($name) {
$this->name = $name;
echo "Создан объект {$this->name}.<br>";
}
function __destruct() {
echo "Уничтожен объект {$this->name}.<br>";
}
}
function outer() {
$obj = new Orator(__METHOD__);
inner();
}
function inner() {
$obj = new Orator(__METHOD__);
echo "Внимание, вбрасывание!<br>";
throw new Exception("Hello!");
}
// Основная программа.
echo "Начало программы.<br>";
try {
echo "Начало try-блока.<br>";
outer();
echo "Конец try-блока.<br>";
} catch (Exception $e) {
echo " Исключение: {$e->getMessage()}<br>";
}
echo "Конец программы.<br>";
?>
Создан специальный класс, который выводит на экран диагностические сообщения в своем конструкторе и деструкторе. Объекты этого класса создаются в первой строке каждой функции.
Результат работы программы выглядит так:
Начало программы.
Начало try-блока.
Создан объект outer.
Создан объект inner.
Внимание, вбрасывание!
Уничтожен объект inner.
Уничтожен объект outer.
Исключение: Hello!
Конец программы.
При вызове throw вначале произошел корректный выход из вложенных функций (с уничтожением всех локальных объектов и вызовом деструкторов), и уж только после этого запустился catch-обработчик. Данное поведение также называют раскруткой стека.
3.5 ИСКЛЮЧЕНИЯ И set_error_handler()
В п.2 рассматривали подход к обработке нефатальных ошибок, а именно установку функции-обработчика посредством вызова функции set_error_handler(). В РНР версии 4 он являлся единственно допустимым методом.
Функция-обработчик имеет один огромный недостаток: в ней неизвестно точно, что же следует предпринять в случае возникновения ошибки.
Сравним явно механизм обработки исключений и метод перехвата ошибок. Рассмотрим пример, похожий на скрипт из листинга 3.1, иллюстрирующий суть проблемы (листинг 3.4).
Листинг 3.4. Файл seh.php
<?php ## Недостатки set_error_handler().
echo "Начало программы.<br>";
set_error_handler("handler");
{
// Код, в котором перехватываются исключения.
echo "Все, что имеет начало...<br>";
// Генерируем ("выбрасываем") исключение.
trigger_error("Hello!");
echo "...имеет и конец.<br>";
}
echo "Конец программы.<br>";
// Функция-обработчик.
function handler($num, $str) {
// Код обработчика.
echo "Ошибка: $str<br>";
// exit();
}
?>
Первое, что бросается в глаза, — это излишняя многословность кода. Но давайте пойдем дальше и посмотрим, какой результат выдает данная программа:
Начало программы.
Все, что имеет начало...
Ошибка: Hello!
За счет использования exit () в функции handler()новая программа не только подвергает сомнению известный тезис (см. операторы echo), но также и утверждает, что любая, даже малейшая, ошибка является фатальной.
Что ж, раз проблема в команде exit(), попробуем ее убрать из скрипта и увидим следующий результат:
Начало программы.
Все, что имеет начало...
Ошибка: Hello!
...имеет и конец.
Конец программы.
И снова мы получили не то, что нужно: ошибка теперь уже не является "чересчур фатальной", как раньше, у нее противоположная проблема: она, наоборот, недостаточно фатальна.
Мы-то хотели разрушать идиому о конечности всего, что имеет начало, а получили — просто робкое замечание, произнесенное шепотом из-за кулис.
Дата: 2019-05-28, просмотров: 207.