Главное меню

Обработка ошибок и исключений

В этом материале рассмотрены концепции обработки исключений и описан способ реализации этой обработки в РНР. Исключения – это унифицированный механизм расширяемой, удобной для обслуживания и объектно-ориентированной обработки ошибок.

Основная идея обработки ошибок состоит в выполнении кода внутри так называемого блока try. Этот блок представляет собой раздел кода следующего вида:

try {

// здесь находится необходимый код

}

В случае возникновения непредвиденных ситуаций внутри блока try можно выполнить так называемую генерацию (throwing) исключения. Некоторые языки, такие как Java, в определенных случаях генерируют исключения автоматически. В РНР исключения нужно генерировать вручную. Это выполняется следующим образом:

throw new Exception ('сообщение', код);

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

Конструктор этого класса принимает два параметра: сообщение и код. Они служат для представления сообщения об ошибке и номера ошибки. Оба эти параметра необязательны.

И, наконец, за блоком try должен следовать как минимум один блок catch, который выглядит так:

catch (указание_типа исключение) {

// обработка исключения

}

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

catch (Exception $е) {

// обработка исключения

}

Объект, передаваемый в блок catch (и перехватываемый им), является тем, который передается (и генерируется) оператором throw, генерирующим исключение. Исключение может быть любого типа, однако удобнее всего использовать либо класс Exception, либо экземпляры собственных пользовательских исключений, унаследованных от класса Exception. (Определение пользовательских исключений рассматривается далее.)

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

И еще один момент: внутри блока catch тоже можно генерировать исключения.

Для большей наглядности рассмотрим пример. Простой пример обработки исключения показан в листинге 7.1.

Обработка ошибок и исключений

В листинге 7.1 видно, что мы воспользовались несколькими методами класса Exception, которые будут рассмотрены несколько позже. Результат выполнения этого кода показан на рис. 7.1.

Обработка ошибок и исключений

В приведенном примере кода было сгенерировано исключение класса Exception. Методы этого встроенного класса можно использовать в блоке catch для вывода полезного сообщения об ошибке.

Класс Exception

В РНР имеется встроенный класс Exception. Как уже упоминалось, конструктор этого класса принимает два параметра: сообщение об ошибке и номер ошибки.

Кроме конструктора, этот класс содержит следующие встроенные методы.

getCode() – возвращает код, переданный конструктору.

getMessage() – возвращает сообщение, переданное конструктору.

getFile() – возвращает полный путь файла кода, в котором возникло исключение.

getLine() – возвращает номер строки в файле кода, где возникло исключение.

getTrace() – возвращает массив, содержащий стек вызовов в месте возникновения исключения.

getTraceAsString() – возвращает ту же информацию, что и метод getTrace, но сформатированную в виде строки.

__toString() – позволяет упростить вывод объекта Exception с помощью оператора echo, предоставляя всю информацию, полученную из перечисленных методов.

Как видим, в коде листинга 7.1 были использованы четыре из этих методов. Эту же информацию (плюс стек вызовов) можно было бы получить с помощью следующего оператора:

echo $е;

Стек вызовов (backtrace) указывает, какие функции выполнялись в момент возникновения исключения.

Исключения, определяемые пользователем

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

Конструкция throw позволяет передавать любые другие объекты. Иногда такая потребность может возникать при наличии проблем, связанных с каким-то конкретным объектом, и необходимости его передачи в целях отладки.

Но, как уже говорилось, в большинстве случаев приходится расширять базовый класс Exception. В руководстве по РНР показан код, который демонстрирует скелет класса Exception. Этот код воспроизведен в листинге 7.2. Обратите внимание, что это не весь код, а лишь те компоненты, которые можно наследовать.

Обработка ошибок и исключений

Основная причина, по которой было приведено определение этого класса, состоит в том, что большинство общедоступных методов являются финальными: т.е. их нельзя переопределить. Можно создать собственный подкласс Exceptions, но нельзя менять поведение базовых методов. Однако можно переопределять функцию __toString(), чтобы изменять способ отображения исключения. Можно также добавлять собственные методы.

Пример определенного пользователем класса Exception показан в листинге 7.3.

Обработка ошибок и исключений

В этом коде объявляется новый класс исключения с именем myException, расширяющий базовый класс Exception. Различие между этим классом и классом Exception состоит в переопределении метода __toString() для обеспечения более "изящного" вывода сообщения об исключении. Результат выполнения этого кода показан на рис. 7.2.

Обработка ошибок и исключений

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

Исключения в приложении "Автозапчасти от Вована"

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

Если вернуться к исходному коду, несложно заметить, что в процессе записи в файл могут возникнуть три проблемы: невозможность открытия файла, невозмбжность получения блокировки или невозможность записи в файл. Для каждой из этих ситуаций мы создали класс исключения. Код этих классов исключений представлен в листинге 7.4.

Обработка ошибок и исключений

Эти подклассы Exception не выполняют никаких действий, представляющих особый интерес. Фактически в этом приложении можно было бы оставить их пустыми или воспользоваться базовым классом Exception. Тем не менее, мы включили в каждый подкласс метод __toString(), который выводит сообщение о типе возникшего исключения.

Чтобы внедрить обработку исключений, мы переписали файл processorder.php, рассмотренный здесь. Новая версия этого файла показана в листинге 7.5.

Обработка ошибок и исключений

Как видите, раздел кода сценария, реализующий файловый ввод-вывод, помещен в блок try. В общем случае использование небольших блоков try, в конце которых выполняется перехват важных исключений, считается правильным подходом к программированию. Это упрощает написание и сопровождение кода обработки исключений, поскольку легко понять, с чем приходится иметь дело.

В случае невозможности открытия файла код генерирует исключение fileOpenException; невозможность блокирования файла ведет к генерированию исключения fileLockException, а невозможность записи в файл – к генерированию исключения fileWriteException.

Взгляните на блоки catch. Для целей иллюстрации мы показали только два таких блока: один для обработки объектов fileOpenException и второй для обработки объектов Exception. Поскольку остальные исключения наследуют свойства и методы класса Exception, они будут перехватываться вторым блоком catch. Сопоставление блоков catch выполняется в соответствии с теми же основными правилами, что и применяемые в операторе instanceof. Это обстоятельство является веским основанием для создания пользовательских классов исключений за счет расширения одного единственного класса.

Важное предупреждение: при генерировании исключения, для которого соответствующий блок catch не был создан, РНР сообщит о фатальной ошибке.

Исключения и другие механизмы обработки ошибок РНР

Помимо механизма обработки исключений, рассмотренного в этой материале, РНР обладает развитой поддержкой обработки ошибок, которая будет описана в дальнейшем. Процесс генерирования и обработки исключений не влияет и не мешает работе данного механизма обработки ошибок.

Обратите внимание, что в листинге 7.5 вызов метода fopen() предварен символом операции подавления ошибки (@). В случае неудачного выполнения этого метода РНР сгенерирует предупреждение, вывод которого на экран или запись в журнал зависит от настроек отчета об ошибках, которые определены в файле php.ini. Эти параметры рассматриваются далее, но следует знать, что данное предупреждение будет сгенерировано независимо от того, выполняется ли генерирование исключения.