Смекни!
smekni.com

Обработка ошибок в коде программ РНР (стр. 6 из 8)

Оператор преобразования в строку _toString() выдает всю информацию, сохраненную в объекте-исключении. При этом используются все свойства объекта, а также вызывается getTraceAsString() для преобразования стека вызовов в строку. Результат, который генерирует метод, довольно интересен (листинг.3.6).

Листинг 3.6. Файл tostring.php

<?php ## Вывод сведений об исключении.

function test($n) {

$e = new Exception("bang-bang #$n!");

echo "<pre>", $e, "</pre>";

}

function outer() { test(101); }

outer();

?>

Выводимый текст будет примерно следующим:

exception 'Exception' with message 'bang-bang #101!' in tostring.php:3

Stack trace:

#0 tostring.php(6): test(101)

#1 tostring.php{7): outer()

#2 (main)

3.8 ИСПОЛЬЗОВАНИЕ ИНТЕРФЕЙСОВ

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

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

Использование интерфейсов вместе с исключениями возможно, начиная с РНР 5.0.1.

Предположим, у нас в программе могут возникать серьезные ошибки следующих основных видов:

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

• файловые (ошибка открытия, чтения или записи в файл);

• сетевые (например, невозможность соединения с сервером);

● пользовательские: сообщения выдаются прямо в браузер.

Для классификации сущностей в программе удобно использовать интерфейсы. Давайте так и поступим по отношению к объектам-исключениям (листинг 3.7).

Листинг3.7. Файлiface/interfaces.php

<?php ## Классификация исключений.

interface IException {}

interface IInternalException extends IException {}

interface IFileException extends IInternalException {}

interface INetException extends IInternalException {}

interface IUserException extends IException {}

?>

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

Теперь, если в программе имеется некоторый объект-исключение, чей класс реализует интерфейс INetException, мы также сможем убедиться, что он реализует и интерфейс IInternalException:

if ($obj instanceof IlnternalException) echo "Этовнутренняяошибка.";

Кроме того, если мы будем использовать конструкцию catch (InternalException...), то сможем перехватить любое из исключений, реализующих интерфейсы IFileException и INetException.

Мы также "на всякий случай" задаем одного общего предка у всех интерфейсов — lException. Вообще говоря, это делать не обязательно.

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

Листинг3.8. Файлiface/exceptions.php

<?php ## Классы-исключения.

require_once "interfaces.php";

// Ошибка: файлненайден.

class FileNotFoundException extends Exception

implements IFileException {}

// Ошибка: ошибкадоступаксокету.

class SocketException extends Exception

implements INetException {}

// Ошибка: неправильныйпарольпользователя.

class WrongPassException extends Exception

implements IUserException {}

// Ошибка: невозможно записать данные на сетевой принтер.

class NetPrinterWriteException extends Exception

implements IFileException, INetException {}

// Ошибка: невозможно соединиться с SQL-сервером.

class SqlConnectException extends Exception

implements IInternalException, IUserException {}

?>

Обратите внимание на то, что исключение типа NetPrinterWriteException реализует сразу два интерфейса. Таким образом, оно может одновременно трактоваться и как файловое, и как сетевое исключение, и перехватываться как конструкцией catch (IFileException ...), так и catch (InetException ...).

За счет того, что все классы-исключения обязательно должны наследовать базовый тип Exception, мы можем, как обычно, проверить, является ли переменная объектом-исключением, или она имеет какой-то другой тип:

if ($obj instanceof Exception) echo "Этообъект-исключение.";

Рассмотрим теперь пример кода, который использует приведенные выше классы (листинг3.9).

Листинг 3.9. Файл iface/test.php

<?php ## Использование иерархии исключений.

require_once "exceptions.php";

try {

printDocument();

} catch (IFileException $e) {

// Перехватываем только файловые исключения.

echo "Файловая ошибка: {$e->getMessage()}.<br>";

} catch (Exception $e) {

// Перехват всех остальных исключений.

echo "Неизвестное исключение: <pre>", $e, "</pre>";

}

function printDocument() {

$printer = "//./printer";

// Генерируемисключениетипов IFileException и INetException.

if (!file_exists($printer))

throw new NetPrinterWriteException($printer);

}

?>

Результатом работы этой программы (в случае ошибки) будет строчка:

Ошибка записи в файл //./printer.


3.9 БЛОКИ-ФИНАЛИЗАТОРЫ

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

3.9.1 Неподдерживаемая конструкция try...finally

В языках программирования Java и Delphi для реализации кода-финализатора имеется очень удобная конструкция try...finally, призванная гарантировать выполнение некоторых действий в случае возникновения исключения или внезапного завершения функции по return. На РНР это можно было бы записать так:

function eatThis() { throw new Exception("bang-bang!"); } function hello() {

echo "Все, что имеет начало, ";

try {

eatThis () ;

} finally {

echo "имеетиконец.";

}

echo "this never prints!"; }

// Вызываемфункцию, hello() ;

Семантика инструкции try...finally должна быть ясна: она гарантирует выполнение finally-блока, даже если внезапно будет осуществлен выход из try-блока.

К сожалению, Zend Engine 2, на которой построен РНР 5, пока не поддерживает конструкцию try...finally, так что приведенный выше код, скорее всего, откажется работать. Почему "скорее всего"? Да потому, что есть все основания полагать, что рано или поздно инструкция finally в РНР появится, поскольку она очень удобна. Возможно, что инструкция finally уже появилась.

3.9.2 "Выделение ресурса есть инициализация"

Как же быть в случае, если нам нужно написать код, который будет обязательно выполнен при завершении работы функции? Единственная на данный момент возможность добиться этого — помещение такого кода в деструктор некоторого класса и создание объекта этого класса непосредственно в функции. Мы знаем, что при выходе из процедуры РНР автоматически уничтожает все переменные-ссылки, созданные внутри тела процедуры. Соответственно, если ссылка на объект будет един­ственной, то вызовется деструктор его класса. В листинге 3.3 мы уже рассматривали такой подход.

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

Если работу с любыми ресурсами в программе реализовать через объекты, то необходимость в finally-блоках просто не возникнет. В самом деле, программа будет сама следить, когда нужно освободить тот или иной ресурс (вызвать деструктор соответствующего объекта), и нам не придется задумываться о явном написании кода освобождения.


3.9.3 Перехват всех исключений

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

echo "Начало программы.<br>";

try {

eatThis ();

}

catch (Exception $e)

{

echo "Неперехваченное исключение: ", $e;

}

echo "Конец программы.<br>";

Таким образом, если в функции eatThis() возникнет любая исключительная ситуация, и объект-исключение "выйдет" за ее пределы (т. е. не будет перехвачен внутри самой процедуры), сработает наш универсальный код восстановления (оператор echo).

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

К сожалению, неожиданные вызовы return в функции при этом не обрабатываются, и отследить их пока нельзя.

Рассмотрим пример функции, которую мы пытались написать выше с использованием try...finally. Фактически, листинг 3.10 иллюстрирует, как можно проэмулировать finally в программе на РНР.

Листинг 3.10. Файл catchall.php

<?php ## Перехват всех исключений.

// Пользовательское исключение.

class HeadshotException extends Exception {}

// Функция, генерирующаяисключение.

function eatThis() { throw new HeadshotException("bang-bang!"); }

// Функция с кодом-финализатором.

function action() {

echo "Все, что имеет начало, ";

try {

// Внимание, опасный момент!

eatThis();

} catch (Exception $e) {

// Ловим ЛЮБОЕ исключение, выводим текст...

echo "имеет и конец.<br>";

// ...а потом передаем это исключение дальше.

throw $e;

}

}

try {

// Вызываем функцию.

action();

} catch (HeadshotException $e) {

echo "Извините, вызастрелились: {$e->getMessage()}";

}

?>

В результате работы программы в браузере будет выведен следующий текст:

Все, что имеет начало, имеет и конец.

Извините, вы застрелились: bang-bang!

Как видите, код-финализатор в функции action() срабатывает "прозрачно" для вызывающей программы: исключение типа HeadsnotException не теряется, а выходит за пределы функции за счет повторного использования throw внутри catch-блока.