скачать рефераты
  RSS    

Меню

Быстрый поиск

скачать рефераты

скачать рефератыУчебное пособие: Обработка ошибок в коде программ РНР

Листинг 3.5. Файл inherit.php

<?php ## Наследование исключений.

// Исключение - ошибка файловых операций.

class FilesystemException extends Exception {

 private $name;

 public function __construct($name) {

 parent::__construct($name);

 $this->name = $name;

 }

 public function getName() { return $this->name; }

}

// Исключение - файл не найден.

class FileNotFoundException extends FilesystemException {}

// Исключение - Ошибка записи в файл.

class FileWriteException extends FilesystemException {}

try {

 // Генерируем исключение типа FileNotFoundException.

 if (!file_exists("spoon"))

 throw new FileNotFoundException("spoon");

} catch (FilesystemException $e) {

 // Ловим ЛЮБОЕ файловое исключение!

 echo "Ошибка при работе с файлом '{$e->getName()}'.<br>";

} catch (Exception $e) {

 // Ловим все остальные исключения, которые еще не поймали.

 echo "Другое исключение: {$e->getDirName()}.<br>";

}

?>

В программе мы генерируем ошибку типа FileNotFoundException, однако, ниже перехватываем исключение не прямо этого класса, а его "родителя" — FilesystemException. Так как любой объект типа FileNotFoundException является также и объектом класса FilesystemException, блок catch "срабатывает" для него. Кроме того, на всякий случай мы используем блок "поимки" объектов класса Exception — "родоначальника" всех исключений. Если вдруг в программе произойдет исключение другого типа (обязательно производного от Exception), оно также будет обработано.

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

3.7 БАЗОВЫЙ КЛАСС Exception

РНР последних версий не допускает использования объектов произвольного типа в качестве исключений. Если вы создаете свой собственный класс-исключение, то должны унаследовать его от встроенного типа Exception.

До сих пор мы пользовались только стандартным классом Exception, не определяя от него производных. Дело в том, что данный класс уже содержит довольно много полезных методов (например, getMessage ()), которые можно применять в программе.

Итак, каждый класс-исключение в листинге 3.5 наследует встроенный в РНР тип Exception. В этом типе есть много полезных методов и свойств, которые мы сейчас перечислим (приведен интерфейс класса):

class Exception {

protected $message; // текстовое сообщение

protected $code; // числовой код

protected $file; // имя файла, где создано исключение

protected $line; // номер строки, где создан объект

private $trace; // стек вызовов

public function__construct([string $message] [,int $code]);

public final function getMessageО; // возвращает $this->message

public final function getCode{); // возвращает $this->code

public final function getFileO; // возвращает $this->file

public final function getLine(); // возвращает $this->line

public final function getTrace();

public final function getTraceAsStringO;

public function __toStringO;

}

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

Мы не будем подробно рассматривать все методы класса Exception, потому что большинство из них выполняют вполне очевидные действия, следующие из их названий. Остановимся только на некоторых. Обратите внимание, что большинство методов определены как final, а значит, их нельзя переопределять в производных классах.

Конструктор класса принимает два необязательных аргумента, которые он записывает в соответствующие свойства объекта. Он также заполняет свойства $fiie, $line и $trace, соответственно, именем файла, номером строки и результатом вызова функции debug_backtrace() (информацию о функциях, вызвавших данную, см. в п. 2).

Стек вызовов, сохраненный в свойстве $trace, представляет собой список с именами функций (и информацией о них), которые вызвали текущую процедуру перед генерацией исключения. Данная информация полезна при отладке скрипта и может быть получена при помощи метода getTrace(). Дополнительный метод getTraceAsString() возвращает то же самое, но в строковом представлении.

Оператор преобразования в строку _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-блока.

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

3.10 ТРАНСФОРМАЦИЯ ОШИБОК

Мы разделили все ошибки на два вида:

● "несерьезные" - диагностические сообщения; перехватываются при помощи set_error_handier();

Страницы: 1, 2, 3, 4, 5


Новости

Быстрый поиск

Группа вКонтакте: новости

Пока нет

Новости в Twitter и Facebook

  скачать рефераты              скачать рефераты

Новости

скачать рефераты

© 2010.