Exceptions в PHP


Оглавление (нажмите, чтобы открыть):

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

Автор: Артемьев Сергей Игоревич
ICQ: 438856621
email: _spin_@bk.ru

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

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

С точки зрения разработчика, основное отличие ошибки от исключения в том, что после возникновения ошибки скрипт может продолжить выполнение, а после возникновения исключения — нет.

Ещё одно различие — для обработки исключений необходимо использовать функции и специализированные языковые конструкции, а для обработки ошибок — только функции.

Исключения — это какие-либо аварийные ситуации, возникающие при выполнении скрипта. В PHP исключение можно сгенерировать («выбросить», «вызвать») и поймать его. Исключение может сенерироваться как интерпретатором, так и разработчиком.

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

Перехват исключения осуществляется с помощью конструкции try. catch. В общем виде эта конструкция записывается так:

Стоит отметить, что блоков catch может быть много, по одному на каждый класс перехватываемых исключений. Таким образом можно создать фильтр исключений, т.е. перехватывать не все, а только избранные типы исключений, а все остальные будут перехвачены стандартным обработчиком PHP.

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

Законный вопрос — зачем самому вызывать ошибку? Рассмотрим простой пример — есть функция формирования отчёта о деятельности компании. Эта функция содержит несколько сотен строк кода, вызывает ещё десяток функций и читает данные из баз данных и файлов. Теперь представим ситуацию, когда одна из баз данных вдруг отключилась, а мы об этом узнали лишь в середине процедуры. Раз нет данных, то и формировать отчёт нет смысла — он будет неполным и некорректным. Но как прервать выполнение основной функции, одновременно сообщив подробности ошибки? Можно сделать с помощью нескольких if..else, но более простым решением будет использование исключений.

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

В рассмотренном примере важную роль играет порядок catch, точнее порядок проверки на тип исключения. Если первым поставить Exception, то все остальные исключения никогда не сработают, т.к. конструкция catch(Exception $ex) перехватывает абсолютно все доступные исключения.

PHP позволяет использовать свой обработчик исключений. Для этого необходимо объявить собственную функцию обработки и зарегистрировать её при помощи функции set_exception_handler().

После этого все возникающие исключения, не обрамлённые конструкцией try. except, будут передаваться в объявленную вами функцию. Например, можно изменить функцию, чтобы она писала все исключения в файл и выдавала пользователю в браузер «красивое» сообщение об ошибке сервера:

Иногда эта функция бывает очень удобной, особенно в процессе отладки скриптов непосредственно на сервере. Если выводить исключения как обычно в браузер — их смогут увидеть посетители, а это не прибавит популярности вашему сайту. Гораздо менее болезненно пользователи воспримут сообщение типа «Извините, сервер находится на обслуживании, попробуйте войти позже.». Поэтому все ошибки и исключения необходимо писать в файлы, а лучше ещё и отправлять администратору на email.

Хорошим тоном при создании сайтов считается полное отсутствие ошибок. Если же ошибки возникают — они должны быть обработаны и представлены пользователю и разработчику в понятном виде.

Исключения

Содержание

Модель исключений (exceptions) в PHP 5 схожа с используемыми в других языках программирования. Исключение можно сгенерировать (как говорят, «выбросить») при помощи оператора throw, и можно перехватить (или, как говорят, «поймать») оператором catch. Код генерирующий исключение, должен быть окружен блоком try, для того чтобы можно было перехватить исключение. Каждый блок try должен иметь как минимум один соответствующий ему блок catch или finally.

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

catch

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

При генерации исключения код следующий после описываемого выражения исполнен не будет, а PHP предпримет попытку найти первый блок catch, перехватывающий исключение данного класса. Если исключение не будет перехвачено, PHP выдаст сообщение об ошибке: «Uncaught Exception . » (Неперехваченное исключение), если не был определен обработчик ошибок при помощи функции set_exception_handler() .

finally

В PHP 5.5 и более поздних версиях также можно использовать блок finally после или вместо блока catch. Код в блоке finally всегда будет выполняться после кода в блоках try и catch, вне зависимости было ли брошено исключение или нет, перед тем как продолжится нормальное выполнение кода. whether an exception has been thrown, and before normal execution resumes.

Примечания

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

Примеры

Пример #3 Выброс исключений

function inverse ( $x ) <
if (! $x ) <
throw new Exception ( ‘Деление на ноль.’ );
>
return 1 / $x ;
>

try <
echo inverse ( 5 ) . «\n» ;
echo inverse ( 0 ) . «\n» ;
> catch ( Exception $e ) <
echo ‘Выброшено исключение: ‘ , $e -> getMessage (), «\n» ;
>

// Продолжение выполнения
echo «Hello World\n» ;
?>

Результат выполнения данного примера:

Пример #4 Вложенные исключения


function inverse ( $x ) <
if (! $x ) <
throw new Exception ( ‘Деление на ноль.’ );
>
return 1 / $x ;
>

try <
echo inverse ( 5 ) . «\n» ;
> catch ( Exception $e ) <
echo ‘Поймано исключение: ‘ , $e -> getMessage (), «\n» ;
> finally <
echo «Первое finally.\n» ;
>

try <
echo inverse ( 0 ) . «\n» ;
> catch ( Exception $e ) <
echo ‘Поймано исключение: ‘ , $e -> getMessage (), «\n» ;
> finally <
echo «Второе finally.\n» ;
>

// Продолжение нормального выполнения
echo «Hello World\n» ;
?>

Результат выполнения данного примера:

Пример #5 Вложенные исключения

class Test <
public function testing () <
try <
try <
throw new MyException ( ‘foo!’ );
> catch ( MyException $e ) <
// повторный выброс исключения
throw $e ;
>
> catch ( Exception $e ) <
var_dump ( $e -> getMessage ());
>
>
>

$foo = new Test ;
$foo -> testing ();

Создание исключений в PHP

В предыдущей статье мы разобрали механизм исключений в PHP. Там мы использовали уже встроенный класс Exception. Однако, мы можем создавать и свои исключения. Как это делается, Вы узнаете из этой статьи.

Создание своих исключений в PHP нужно, когда требуется обработка только определённого типа ошибок. Какие могут быть типы? Можно, например, сделать исключения для работы с базой данных или исключения для проверки правильности введённых пользователем данных, или исключения для работы с файлами. Это всё отдельные типы исключений, которые мешать в 1 класс Exception будет, мягко говоря, странно. Более того, можно не просто создать 1 класс для 1-го типа ошибок, но и вообще для каждой ошибки создать отдельный класс.

Чтобы создать исключение в PHP, нужно объявить новый класс, наследуемый от класса Exception:

getCode() == 1) echo «Файл не найден»;
elseif ($e->getCode() == 2) echo «Файл закрыт для записи»;
elseif ($e->getCode() == 3) echo «Ошибка при открытии файла»;
>
?>

Обратите внимание, что в catch() мы уже ждём не просто Exception, а именно FileException. Таким образом, если будет throw new Exception(), то в этот блок мы просто не попадём, так как он ждёт исключительно FileException и его дочерние классы. С другой стороны, если мы напишем в catch просто Exception, то такой catch будет перехватывать Exception и все его дочерние классы, в том числе, и FileException. Но это уже относится больше к полиморфизму, нежели к обработке исключений.

И, наконец, давайте разберём с Вами вариант, где за каждую ошибку отвечает отдельный класс:

Код достаточно прозрачный, поэтому в комментировании не нуждается. Отмечу лишь то, что такой подход несколько более читабелен. Намного понятнее new FileNotExistsException(), чем new FileException(1). А минус данного подхода — создание огромного количества классов, что, впрочем, не так критично.

Вот таким образом можно создавать свои собственные исключения в PHP. Конечно, были разобраны далеко не все возможности, однако, я придерживаюсь принципа: рассказывать не о том, что я знаю, а о том, что я действительно использую в своей практике. А тем, кто хочет знать всё, можно посмотреть в справочнике подробнее обо всех возможностях исключений в PHP.

Копирование материалов разрешается только с указанием автора (Михаил Русаков) и индексируемой прямой ссылкой на сайт (http://myrusakov.ru)!

Добавляйтесь ко мне в друзья ВКонтакте: http://vk.com/myrusakov.
Если Вы хотите дать оценку мне и моей работе, то напишите её в моей группе: http://vk.com/rusakovmy.

Если Вы не хотите пропустить новые материалы на сайте,
то Вы можете подписаться на обновления: Подписаться на обновления

Если у Вас остались какие-либо вопросы, либо у Вас есть желание высказаться по поводу этой статьи, то Вы можете оставить свой комментарий внизу страницы.

Порекомендуйте эту статью друзьям:

Если Вам понравился сайт, то разместите ссылку на него (у себя на сайте, на форуме, в контакте):

Она выглядит вот так:

  • BB-код ссылки для форумов (например, можете поставить её в подписи):
  • Комментарии ( 0 ):

    Для добавления комментариев надо войти в систему.
    Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.

    Copyright © 2010-2020 Русаков Михаил Юрьевич. Все права защищены.

    Исключения в PHP 7

    С PHP 7 к нам приходит новая эпоха обработки исключений. Впервые PHP стал выкидывать исключения для фатальных и восстанавливаемых фатальных ошибок. Теперь мы можем их обрабатывать, как захотим. Больше никаких @, include, file_exists и прочей не логичности в коде.

    В корне исключений в PHP 7 теперь лежит интерфейс Throwable от которого происходит реализация (implementations) всех корневых исключений и исключений для фатальных и восстанавливаемых фатальных ошибок (если не обработать, ошибки останутся).

    Новые исключения Error

    Как вы уже смогли заметить из таблицы иерархий, в PHP 7 добавились новые исключения для отлавливания фатальных (E_ERROR) и восстанавливаемых ошибок (E_RECOVERABLE_ERROR).
    Но к сожалению, определенные ошибки, например «out of memory», по прежнему приведут к остановке, как их вообще обрабатывать? ��


    Error

    Все фатальные ошибки будут выбрасывать исключение расширяющие Error исключения. Ошибки при этом, будут выведены как и раньше, если исключение не обработано.

    AssertionError

    В PHP 7, есть усовершенствование утверждений, используя assert() функцию, с добавлением нулевой стоимостью утверждений, и выбрасыванием исключений. Для включения данной возможности, нужно установить параметр assert.exception = 1 в php.ini (или с использованием ini_set()).

    ParseError

    Благодаря выкидыванию исключений ParseError при ошибке включения файла, теперь можно обработать поведение:

    TypeError

    Исключения TypeError выбрасываются, когда аргументы метода или возвращаемое значение не совпадает с объявленным типом.

    Отлавливаем фатальные ошибки в PHP7

    Еще одно важное изменение PHP7 в отлавливании фатальных ошибок. Раньше, они ловились и обрабатывались с использованием set_error_handler() . Теперь, в PHP 7, они выкидывают Error исключения, и если, исключения не будут обработаны, то выбрасывается реальная фатальная ошибка, которая больше не будет доступна в set_error_handler() .

    Это было сделано для обратной совместимости и для работы в PHP 5.x и 7, вы должны использовать both set_error_handler() и try. catch .

    Использование Throwable

    В PHP 7, у нас есть общий интерфейс для исключений, и мы могли бы создать наши собственные исключения в иерархии исключений для полной настройки исключений (извиняюсь за тавтологию), просто реализуя Throwable интерфейс. Но к сожалению, мы этого сделать не можем, это означает, что мы все еще должны по-прежнему расширять либо Exception, либо Error, и не можем непосредственно реализовывать Throwable в пользовательских классах.

    Но это еще не вся история, есть лайфхак, который не рекомендуется к использованию. Вы можете расширить интерфейс Throwable — а уже потом, реализовать свои исключения типов Error или Exception используя свой расширенный интерфейс.

    Заключение

    Изменения в исключениях на самом деле довольно значительные, теперь PHP позволяет нам изящно обрабатывать почти все фатальные ошибки. Тот факт, что разработчики PHP смогли сохранить почти полную обратную совместимость, не может не радовать!

    В чем принцип записи catch(Exception $e) в php?

    Здесь мне все понятно в свойство someFunction класса AClass может попасть только экземпляр класса BClass или вот пример

    И прошу заметить мы туда подставляем значения только когда вызываем функцию или делаем экземпляр класса а вот что эта за форма записи — я не понимаю — расшифруйте пожалуйста — я же нигде не указывал $e и вообще могу вместо $e написать любую переменную

    1 ответ 1

    Да, вы правы, вместо $e вы можете написать любую переменную. Но вся суть в том, что catch — это не функция, а перехватчик исключений. Этот механизм работает в всязке с try.

    \Exception — это базовый класс для всех исключений от которого вы можете наследовать свои исключения. Exception e- говорит о том, что вы хотите перехватывать все исключения.

    Мастер Йода рекомендует:  Senior UE4 Developer

    если вы наследуете свой тип исключения например:

    то перехватываться будут только ваши исключения из CustomException.

    Наследование исключений выглядит так Наследование исключений

    Хорошая ли практика создавать свои классы Exception для отлавливания разных ошибок?

    Я только недавно изучил механизм Exception’ов и сейчас их осваиваю. Вот пара вопросов по поводу их применения:

    Можно ли создавать свои пустые классы Exception’ов наследуемые от класса Exception только лишь для того что бы было отдельное имя исключения? Например: я работаю с файлами, и если файл не удалось загрузить файл — я бросаю исключение

    Сам класс `FileException ничего не содержит:

    И второй вопрос, который вытекает из первого. Делаю я это для того что бы отловить различные ошибки. Например при добавлении сообщения на сайте, у меня может возникнуть разные исключения, например: Не корректное сообщение, Не удалось загрузить файл, ошибка PDO. То есть в контроллере это будет выглядеть так:

    Можно ли так делать? Это нормально что в контроллере столько много catch’eй? На сколько это хорошая практика? Или я не правильно понял механизм работы исключений?

    update: И как быть, если у меня возникает несколько исключений, и я хочу выдать пользователю информацию о всех возникших сразу?

    • Вопрос задан более года назад
    • 1441 просмотр


    Практика хорошая. Именно так и стоит делать. В идеале у каждой ошибки должен быть свой уникальный эксепшн. Например, от FileException можно наследовать например NotFoundFileException и AccessFileException. При этом тело классов в большинстве случаев будет пустым.

    А вот то, что ты пытаешься поймать все возможные эксапшны в контроллере — это совсем не хорошо. По хорошему нужен отдельный эксепшн хендлер для этого.

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

    Ну и по поводу контроллера, конечно часть исключений там поймать можно, но вот FileException, PDOException и уж тем более Exception к таковым явно не относятся.

    > В идеале у каждой ошибки должен быть свой уникальный эксепшн.
    Зачем? Это можно оправдать, если вы пишете какую-то опенсорс библиотеку и вы не можете предсказать как она будет использоваться. И то при наличии продуманной иерархии ошибок, а не просто каждому throw new по своему классу.

    Если же это ваш проект, в котором вы знаете как это будет использоваться, и в который, если появятся новые требования, можно внести изменения — то зачем? (Я предполагаю что добавление нового подтипа ошибки не должно вызывать изменения во всем проекте).

    Зачем вам AccessFileException?
    Если вы после этой ошибки программно выставляете нужные права — то ок.
    Если вы просто выводите сообщение — то зачем?

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

    WhoMe, а затем, что сегодня это твой проект, завтра твой и Васин, а послезавтра Васин и Димин, а послепослезавтра Димин и Ванин. И от того, что ты когда-то знал (но уже давно забыл) как там все раньше работало Диме и Ване не легче.

    Именно то, что каждый программист херачит код исключительно под себя любимого является основной причиной говнокода во всех проектах.

    Зачем вам AccessFileException?
    Если вы после этой ошибки программно выставляете нужные права — то ок.
    Если вы просто выводите сообщение — то зачем?

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

    Проще всего поддерживать код которого нет.

    Именно то, что каждый программист херачит код исключительно под себя любимого является основной причиной говнокода во всех проектах.

    Согласен (но не основной). Именно поэтому я предлагаю создавать Exception-ы под реальные требования проекта, а не под себя любимого.

    FileException бросается в 100 разных мест, найти те места, где это происходит из-за отсутствия доступа к файлу будет аццки тяжело.

    Всмысле?
    Стек трейс никто не отменял. Мы ведь про php говорим, да?
    Ты же сообщение об ошибке то туда положишь? throw new FileException(«Нет доступа к файлу»);
    У тебя же есть текстовый редактор который найдет все «new FileException», да?
    Че тут аццкого?

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

    2 из 10 случаев, в которых это понадобится, стоит того, чтоб написать их в оставшихся 8. Утверждение всегда верно, какие бы цифры ты не взял с потолка. Время, которое ты затратишь на эксепшны в этих условных 8 — ничтожно мало, по сравнению с временем на исправление условных 2, если это бы не было сделано сразу.

    Согласен (но не основной). Именно поэтому я предлагаю создавать Exception-ы под реальные требования проекта, а не под себя любимого.

    Нет ничего хуже самобытности в проекте. Но если у тебя другой опыт — то расскажи о нем.

    И опять же, «работа с исключениями» и «требования проекта» находятся в разных плоскостях бытия и никогда не пересекаются. Никогда не встречал требования по работе с исключениями и даже вообразить себе такое не могу.

    Стек трейс никто не отменял. Мы ведь про php говорим, да?
    Ты же сообщение об ошибке то туда положишь? throw new FileException(«Нет доступа к файлу»);
    У тебя же есть текстовый редактор который найдет все «new FileException», да?
    Че тут аццкого?

    UPD:
    > Это нормально что в контроллере столько много catch’eй?
    Не то чтобы нет, но «попахивает».
    > как быть, если у меня возникает несколько исключений, и я хочу выдать пользователю информацию о всех возникших сразу

    У вас смешана валидация входных параметров с возникновением нештатных ситуаций.
    Exception стоит кидать если метод не может вернуть осмысленный результат.
    Ошибки валидации («сообщение слишком длинное», «файл не того расширения») можно собирать, к примеру, в массив. Как валидировать входные данные выходит за рамки этого вопроса.
    ——
    > Можно ли так делать?
    Попробуйте использовать правило 5 почему (ну или «зачем») чтобы попытаться добраться до сути.

    — Я даю отдельные имена исключениям.
    — Зачем?
    — Для того что бы отловить различные ошибки.
    — Зачем отлавливать различные типы ошибок?
    — Чтобы показывать разные сообщения.
    — Зачем показывать разные сообщения?
    — Чтобы пользователь мог отреагировать определенным образом на конкретный тип ошибки
    (решить что ему делать в случае конкретного исключения).

    Давайте посмотрим что человек (пользователь) на вашем сайте (системе) может сделать в случае ошибки:
    1. Не корректное сообщение.
    Привести его к корректному (сделать его короче, как-то отформатировать и т.п.)

    2. Не удалось загрузить файл.
    Если файл слишком большой — можно попробовать загрузить файл поменьше.
    Если это внутренняя ошибка (например: закончилось место на диске на сервере, нет прав на запись и т.п) — то ничего, здесь уже требуется вмешательство тех кто обслуживает систему.

    3. Ошибка PDO.
    Ничего, это внутренняя ошибка.

    Т.е. есть смысл создавать новый тип исключения, если пользователь системы может иначе отреагировать на этот тип.

    Я использую 2 типа исключений на уровне web-слоя: внешние и внутренние.
    Внешние — если пользователь может повлиять на них (страница не найдена, файл слишком большой, нет доступа и т.п.).
    В них сообщение показывается пользователю как есть. Подноготная (пути к файлам, пароли, названия БД и т.п) в такие ошибки не вкладывается.

    Внутренние — пользователь не может влиять (ошибка подключения к БД, SQL syntax error и т.п).
    Пользователю показывается только код ошибки. В такие ошибки также вкладываю техническую информацию.

    Ты (как программист) также можешь быть пользователем какой-то системы (к примеру, библиотеки).
    Например, библиотеки для работы с БД.
    Предположим ты импортируешь csv-файл в базу, заполняешь числовую колонку и вместо числа у тебя попадается текст.
    Запрос падает с ошибкой и тебе не плохо бы отличать её от ошибки синтаксиса.
    Если попался текст вместо числа — то можно просто пропустить строку.
    Если ошибка синтаксиса — то у ошибка в коде и продолжать импорт нет смысла.

    > Можно ли создавать свои пустые классы Exception’ов наследуемые от класса Exception только лишь для того что бы было отдельное имя исключения?

    Сколько делать отдельных исключений?
    Зависит от того как их будет обрабатывать пользователь.
    Если твой контроллер(модель, подсистема) кидает множество разных ошибок и все они обрабатываются одинаково — то стоит призадуматься.

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


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

    Что такое исключение?

    В PHP5 появился новый объектно-ориентированный способ борьбы с ошибками.

    Обработка исключений используется для изменения нормального потока выполнения кода, если возникает указанное (исключительное) условие ошибки. Это условие, называется исключение

    Это то, что обычно происходит, когда срабатывает исключение:

    • Текущее состояние кода сохраняется
    • Выполнение кода переключится на предопределенную (пользовательскую) функцию, обработчик исключений
    • В зависимости от ситуации обработчик может, возобновить выполнение из сохраненного состояния кода, завершить выполнение сценария или продолжить сценарий из другого расположения в коде

    Мы покажем различные методы обработки ошибок:

    • Основное использование исключений
    • Создание пользовательского обработчика исключений
    • Несколько исключений
    • Повторное создание исключения
    • Установка обработчика исключений верхнего уровня

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

    PHP Основное использование исключений

    При возникновении исключения, следующий за ним код не будет выполнен и PHP будет пытаться найти соответствующий блок «Поймать».

    Если исключение не перехватывается, фатальная ошибка будет выдаваться как сообщение «Необработанное исключение».

    Попробуем выбросить исключение, не поймав его:

    Пример

    1) <
    throw new Exception(«Значение должно быть 1 или ниже»);
    >
    return true;
    >

    //Вызов исключения
    checkNum(2);
    ?>

    Код выше получит ошибку, как эта:

    PHP Пробовать, забросить и вытащить

    Чтобы избежать ошибки из приведенного выше примера, Вы должны создать код для обработки исключения.

    Правильный код исключения должен включать:

    1. Пробовать — используется исключение в блоке try . Если исключение не срабатывает, код будет работать как обычно. Однако, если исключение срабатывает, исключение будет thrown
    2. Забросить — вызывает исключение. Каждый throw должен иметь хотя бы один catch
    3. Вытащить — Один блок catch получает исключение и создает объект, содержащий сведения об исключении

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

    Пример

    1) <
    throw new Exception(«Значение должно быть 1 или ниже»);
    >
    return true;
    >

    //вызвать исключения в блоке «try»
    try <
    checkNum(2);
    //Если исключение выбрасывается, этот текст не будет отображаться
    echo ‘Если Вы видите это, значение должно быть 1 или ниже’;
    >

    //исключение catch
    catch(Exception $e) <
    echo ‘Сообщение: ‘ .$e->getMessage();
    >
    ?>

    Код выше получит ошибку, как эта:

    Объяснение примера:

    Приведенный выше код создает исключение и перехватывает его:

    1. Функция создает checkNum() . Она проверяет, если число больше, чем 1. Если это так, то исключение throw
    2. функция вызывается checkNum() в блок try
    3. Исключение checkNum() в функции thrown
    4. Блок catch получает исключение и создает объект $е , содержащий сведения об исключении
    5. Сообщение об ошибке из исключения повторяется вызовом $e->getMessage() из объекта исключение

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

    PHP Создание пользовательского класса исключений


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

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

    Позволяет создать класс исключения:

    Пример

    getLine().’ в ‘.$this->getFile()
    .’: ‘.$this->getMessage().’ не является допустимым адресом электронной почты’;
    return $errorMsg;
    >
    >

    $email = «someone@example. com»;

    try <
    //проверить, если
    if(filter_var($email, FILTER_VAL > //исключение throw, если e-mail не является допустим
    throw new customException($email);
    >
    >

    catch (customException $e) <
    //показать пользовательское сообщение
    echo $e->errorMessage();
    >
    ?>

    Новый класс является копией старого класса исключений, с добавлением функции errorMessage() . Так как это копия старого класса и она наследует свойства и методы из старого класса, мы можем использовать класс исключений метод вызова getline() , метод getfile() и метод getmessage() .

    Объяснение примера:

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

    1. Класс customException() создается как расширение старого класса исключений. Таким образом, он наследует все методы и свойства из старого класса исключений
    2. Функция errorMessage() создана. Данная функция возвращает сообщение об ошибке, если адрес электронной почты является недействительным
    3. Переменной $email присваивается строка, которая не является допустимым адресом электронной почты
    4. Блок try выполняется и возникает исключение, так как адрес электронной почты является недопустимым
    5. Блок catch вытаскивает исключение и отображает сообщение об ошибке

    Несколько исключений

    Сценарий может использовать несколько исключений для проверки нескольких условий.

    Можно использовать несколько блоков if..else , switch или вложенных несколько исключений. Эти исключения могут использовать различные классы исключений и возвращать различные сообщения об ошибках:

    Пример

    getLine().’ в ‘.$this->getFile()
    .’: ‘.$this->getMessage().’ не является допустимым адресом электронной почты’;
    return $errorMsg;
    >
    >

    try <
    //проверить, если
    if(filter_var($email, FILTER_VAL > //исключение throw, если e-mail не является допустим
    throw new customException($email);
    >
    //проверить «example» в почтовом адресе
    if(strpos($email, «example») !== FALSE) <
    throw new Exception(«$email является example e-mail»);
    >
    >

    catch (customException $e) <
    echo $e->errorMessage();
    >

    catch(Exception $e) <
    echo $e->getMessage();
    >
    ?>

    Объяснение примера:

    Приведенный выше код проверяет два условия и создает исключение, если какое-либо из условий не выполняется:

    1. Класс customException() создается как расширение старого класса исключений. Таким образом, он наследует все методы и свойства из старого класса исключений
    2. Создается функция errorMessage() ). Эта функция возвращает сообщение об ошибке, если адрес электронной почты является недействительным
    3. Переменная $email устанавливается в строку, которая является допустимым адресом электронной почты, но содержит строку «example»
    4. Блок try выполняется и исключение не возникает при первом условии
    5. Второе условие вызывает исключение, так как сообщение электронной почты содержит строку «example»
    6. Блок catch вытаскивает исключение и отображает правильное сообщение об ошибке

    Если исключение было из класса customException и не было customException поймано, только исключение catch, исключение будет сделано так.

    PHP Повторное создание исключений

    Иногда, когда возникает исключение, может потребоваться обработать его по-другому, чем стандартный способ. Можно забросить исключение во второй раз в блок «catch».

    Мастер Йода рекомендует:  Курс «Алгоритмы и структуры данных»

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

    Пример

    getMessage().’ не является допустимым адресом электронной почты.’;
    return $errorMsg;
    >
    >

    try <
    try <
    //проверить «example» в почтовом адресе
    if(strpos($email, «example») !== FALSE) <
    //исключение throw, если электронная почта не является допустимой
    throw new Exception($email);
    >
    >
    catch(Exception $e) <
    //повторного исключение throw
    throw new customException($email);
    >
    >

    catch (customException $e) <
    //показать пользовательское сообщение
    echo $e->errorMessage();
    >
    ?>

    Объяснение примера:

    Приведенный выше код проверяет, содержит ли адрес электронной почты строку «example», если содержит, исключение thrown создается повторно:

    1. Класс customException () создается как расширение старого класса исключений. Таким образом, он наследует все методы и свойства из старого класса исключений
    2. Создается функция errorMessage(). Эта функция возвращает сообщение об ошибке, если адрес электронной почты является недействительным
    3. Переменная $email устанавливается в строку, которая является допустимым адресом электронной почты, но содержит строку «example»
    4. В блоке «try» содержится еще один блок «try», чтобы повторно сгенерировать исключение throw
    5. Исключение срабатывает с момента электронной почты, содержит строку «example»
    6. Блок «catch» перехватывает исключение и повторно забрасывает «customException»
    7. «customException» перехватил и отображает сообщение об ошибке


    Если исключение не поймано в ее текущей блоке «try», он будет искать блок catch «более высокого уровня».

    PHP Установка обработчика исключений верхнего уровня

    Функция set_exception_handler() задает пользовательскую функцию для обработки всех необработанных исключений:

    Пример

    throw new Exception(‘Произошло необработанное исключение’);
    ?>

    Выходные данные приведенного выше, код должны быть примерно такими:

    В коде выше нет блока «catch». Вместо этого сработал обработчик исключений верхнего уровня. Эта функция должна использоваться для отслеживания необработанных исключений.

    Правила для исключений

    • Код может быть окружен блоком try, чтобы помочь поймать потенциальные исключения
    • Каждый блок try или «throw» должны иметь по крайней мере один соответствующий блок catch
    • Для перехвата различных классов исключений можно использовать несколько блоков catch
    • Исключения могут быть брошены (или повторно брошены) в блоке catch в блоке try

    Простое правило: если Вы что-то бросаете, вы должны поймать его.

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

    До недавнего времени программистам приходилось проявлять незаурядную изобретательность в поиске способов устранения ситуаций возникновения ошибок в сценариях на языке PHP. Для этого они либо задавали и выводили строки с описанием ошибок, либо пытались воспользоваться всеми возможными средствами ограниченной системы регистрации ошибок. Несмотря на наличие других многочисленных полезных средств, интерпретатор PHP не поддерживал качественную систему, позволяющую успешно справляться с ситуациями возникновения ошибок. К счастью, с выходом версии PHP 5 ситуация изменилась в лучшую сторону.

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

    Программисты, знакомые с такими языками структурного программирования, как C# и Java, по-видимому, давно привыкли использовать различные встроенные объекты, позволяющие справляться с ошибками и исключительными ситуациями. Таким специалистам будет приятно узнать, что теперь в версии PHP 5 впервые появился объект, предназначенный для обработки исключительных ситуаций, и что синтаксические конструкции для работы с этим объектом весьма напоминают существующие языки, такие как Java. В действительности даже после краткого знакомства с этими синтаксическими конструкциями разработчик может приступить к обработке ошибок и исключительных ситуаций в языке PHP во многом по такому же принципу, который применяется в других объектно-ориентированных языках.

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

    Ошибки и исключительные ситуации

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

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

    Ниже приведен пример кода, который содержит некоторые средства обнаружения ошибок в том виде, в каком они могло быть реализованы в версии PHP 4 или в одной из предыдущих версий. В этом коде осуществляется выборка переменной POST, содержащей идентификатор пользователя. Такой идентификатор должен иметь длину по меньшей мере девять символов и начинаться с префикса «usr»:

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

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

    Класс Exception

    В версии PHP 5 предусмотрен новый класс Exception, который может непосредственно использоваться в любом коде, выполняемом под управлением сервера PHP 5 или более поздней версии. В примере, приведенном выше, применялись булевы функции, а класс Exception позволяет создавать или активизировать в коде исключительные ситуации — экземпляры этого класса.

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

    Код PHP Использование исключений

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

    Перехват и обработка исключительных ситуаций осуществляется с помощью управляющей конструкции try/catch. Прежде всего необходимо включить любой код, выполнение которого может привести к возникновению ошибки или исключительной ситуации, в конструкцию try(). При активизации в этом коде любой исключительной ситуации выполнение блока try() прекращается; это означает, что оставшийся код в конструкции try() не выполняется. Затем блок catch() просматривается для поиска исключительной ситуации соответствующего типа, а обработка исключительной ситуации происходит с помощью кода, содержащегося в данном конкретном блоке catch. Благодаря такой организации работы может быть предусмотрен анализ различных условий с учетом типа активизированной исключительной ситуации, но не предпринимается безнадежная попытка справиться с какой-то общей, неконкретизированной ошибкой.

    Активизация исключительной ситуации

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

    Ниже приведен код, который показывает, как можно активизировать и сразу же перехватить универсальную исключительную ситуацию, а затем применить методы объекта Exception для извлечения переданного сообщения, кода ошибки, имени файла и номера строки, ставшей источником ошибки:


    Код PHP Получение дополнительной информации об исключении

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

    Определение собственных подклассов Exception

    Язык PHP позволяет также определять собственные классы, которые наследуют методы класса Exception. Это означает, что можно больше не ограничиваться использованием лишь функции getMessage() для получения информации о том, какой именно тип имеет возникшая ошибка. Подклассы могут быть определены так, как показано в следующем примере:

    Рассмотрим пример, приведенный ниже, который демонстрирует способы использования специализированных исключительных ситуаций. Этот код предназначен для обеспечения взаимодействия с пользователями, подписывающимися на определенные услуги. Пользователи могут забыть включить обязательный префикс «usr» в свой идентификатор пользователя поэтому при отсутствии этого префикса можно предусмотреть возможность добавить его и снова проверить подлинность пользователя, а не прекращать выполнение программы:

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

    Ограничения средств обработки исключений языка PHP

    Объект Exception — новое средство, появившееся в версии PHP 5, поэтому этот объект, как таковой, все еще находится на самых ранних этапах разработки. Ко времени написания этих строк язык PHP не поддерживал использование таких конструкций, как finally() и throws(), предусмотренных в языке Java и других языках. Кроме того, в отличие от других языков на исключительные ситуации еще не отображаются ошибки, распознаваемые самим интерпретатором PHP (в том числе ошибки, сообщения о которых обычно появляются в клиентском браузере). Из-за этого, например, возникновение ошибки в операторе SQL, выполняемом в блоке try/catch, не приводит автоматически к активизации исключительной ситуации, которую можно было бы перехватить и заняться ее обработкой. Такие удобные функциональные средства, по всей вероятности, должны быть включены в одну из будущих версий PHP, поэтому для авторов имеет смысл упомянуть о них, а для читателей — следить за их появлением.

    Про ошибки и исключения в PHP

    Данная статья ни в коем случае не претендует на пересказ мануала, а содержит некие размышления на тему генерации эрроров и использования исключений в PHP5:

    1. Errors & exceptions. В чем отличия и что когда использовать
    2. Каким образом можно отлавливать Fatal Error-ы (E_ERROR)
    3. Многоуровневая обработка исключений и разматывание стека вызовов методов

    I. Самое первое и пожалуй самое важное. В чем отличие эрроров от исключений.
    В принципе и то и другое появляется в результате некой нестандарной операции в ходе работы скрипта. Эрроры можно разделить на два типа:
    1). пользовательские E_USER_NOTICE, E_USER_WARNING, E_USER_ERROR (то есть которые генерятся в ходе работы скрипта при вызове функции trigger_error())
    2). и все остальные — E_NOTICE, E_WARNING, E_ERROR, E_PARSE, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING и пр. (которые появляются при нестандартной работе библиотечных функций, при неверном использовании лексем языка, при некорректной настройке интерпритатора PHP ну или же в результате банальных косяков программистов в духе «забыл закрывающую скобку»).
    Исключения же подобно эррорам пользовательского типа генерятся в ходе работы скрипта при помощи инструкции:

    и перехватываются в блоках:

    Стоит различать в каком случае использовать генерацию пользовательских ошибок, а в каком — выброс Exceptions. Генерация пользовательских ошибок имеет смысл в случае, когда к примеру пишется некая функция (метод) и подразумевается, что программист будет использовать данную функцию неверно. Примеры: некорректные аргументы функции, ошибка при выполнении SQL запроса внутри функции, отсутствие прав доступа к необходимому файлу. Тогда программиста следует уведомить об этом и бросается E_USER_NOTICE, E_USER_WARNING или же E_USER_ERROR. Вот например:

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

    В данном примере в зависимости от URL скрипт пытается найти контроллер, а если ничего не найдено, то генерится страница для отображения 404 ошибки с неким сообщением.
    То есть в общем случае генерация error / notice / warning — это всегда ошибка (бага), которая должна быть исправлена и которая не должна повторяться в принципе. Это, так сказать, уведомление для программиста. Исключение же (exception) генерится далеко не всегда в результате ошибки, и может появляться при абсолютно правильной работе приложения, но при нестандратных пользоваетльских данных (как в примере выше — нестандартный URL).
    Так же стоит отметить что функциональность исключений стоит использовать при написании библиотек, которые могут быть заюзаны в различных системах и на различных сайтах. Например, мы пишем некий ORM для работы с DB — набор классов с кучей различных методов. Для обобщения пусть у нас будет некий класс Table и у него будет метод query() для выполнения произвольного SQL:

    Заместо того, чтобы бросать E_USER_ERROR при некорректном запросе, стоит внутри метода query() генерить Exception. Тогда программист, работающий с данной библиотекой сможет использовать простую конструкцию следующего вида, не вдаваясь в детали реализации библиотеки:

    II. Вообще говоря пользователь сайта ни при каких обстоятельствах не должен знать, что произошла какая-то ошибка. Так как чисто белая страница в ответ на некоторые запросы может ввести пользователей в ступор и сильно подпортить рейтинг веб-проекта в глазах людей. Поэтому даже если генерится эррор, нужно так или иначе отображать более-менее приличную страницу с более-менее приличным сообщением в духе «Извини, в данный момент страница недоступна, попробуй позже». Ошибки вида E_USER_ERROR перехватываются легко при помощи функции set_error_handler(), а вот чтобы перехватывать Fatal Error-ы (E_ERROR) нужно немного изловчиться.
    Например вот так:

    III. Многоуровневая обработка исключений и разматывание стека вызовов методов. При помощи механизма try / catch / exceptions можно построить приложение так, что в случае генерации эксепшена в неком методе, закопанном глубоко в недрах кода, можно воспроизвести стек вызовов методов (которые привели к данной ситуации) с подробным описанием возникшего исключения.
    Например:

    тогда при выполнении данного кода мы получим сообщение на подобии:

    exception ‘Exception’ with message ‘Error in method C::doException !’ in /srv/www/localhost/web/index.php:40
    Stack trace:
    #0 /srv/www/localhost/web/index.php(32): C->doException()
    #1 /srv/www/localhost/web/index.php(17): B->doSomething()
    #2 /srv/www/localhost/web/index.php(46): A->run()
    #3

    Как видно, при вызове метода A::run() генерится Exception, «закопанный» глубоко внутри методов связных классов B и C. При возникновении исключения мы движемся по стеку вызова методов, в поисках блока перехватчика try < >catch < >, а при нахождении такового в блоке catch < >можем корректно обработать исключение и даже, при желании, бросить ещё один Exception (такая ситуация может возникнуть в случае если мы хотим вызвать кучу методов со сложной логикой — например для сохранения сообщения в БД).
    Вот например:

    Мастер Йода рекомендует:  Как пройти собеседование в Google советы по подготовке

    тут задаются два типа пользовательских исключений: NotFoundException и CommonException, которые наследуют стандартный класс Exception (кстати говоря, можно прееопределять стандартные методы класса Exception). И как видно из кода исключение типа CommonException может быть брошено как при вызове функций findController() и run(), так и при вызове функции show404page() (уже после выброса NotFoundException). То есть за одно обращение к скрипту Exceptions-ы могут бросаться два раза.

    Exceptions в PHP

    Освойте бесплатно наиболее простой, быстрый и гибкий способ создавать адаптивные веб-сайты.

    Дизайн лендинга

    Создавайте дизайн любых сайтов — для себя и на заказ!

    Популярное

    • Главная
    • ->
    • Материалы
    • ->
    • Обработка ошибок в PHP (класс Exception)

    Reg.ru: домены и хостинг

    Крупнейший регистратор и хостинг-провайдер в России.

    Более 2 миллионов доменных имен на обслуживании.


    Продвижение, почта для домена, решения для бизнеса.

    Более 700 тыс. клиентов по всему миру уже сделали свой выбор.

    Бесплатный Курс «Практика HTML5 и CSS3»

    Освойте бесплатно пошаговый видеокурс

    по основам адаптивной верстки

    на HTML5 и CSS3 с полного нуля.

    Фреймворк Bootstrap: быстрая адаптивная вёрстка

    Пошаговый видеокурс по основам адаптивной верстки в фреймворке Bootstrap.

    Научитесь верстать просто, быстро и качественно, используя мощный и практичный инструмент.

    Верстайте на заказ и получайте деньги.

    Что нужно знать для создания PHP-сайтов?

    Ответ здесь. Только самое важное и полезное для начинающего веб-разработчика.

    Узнайте, как создавать качественные сайты на PHP всего за 2 часа и 27 минут!

    Создайте свой сайт за 3 часа и 30 минут.

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

    Вам останется лишь наполнить его нужной информацией и изменить дизайн (по желанию).

    Изучите основы HTML и CSS менее чем за 4 часа.

    После просмотра данного видеокурса Вы перестанете с ужасом смотреть на HTML-код и будете понимать, как он работает.

    Вы сможете создать свои первые HTML-страницы и придать им нужный вид с помощью CSS.

    Бесплатный курс «Сайт на WordPress»

    Хотите освоить CMS WordPress?

    Получите уроки по дизайну и верстке сайта на WordPress.

    Научитесь работать с темами и нарезать макет.

    Бесплатный видеокурс по рисованию дизайна сайта, его верстке и установке на CMS WordPress!

    Хотите изучить JavaScript, но не знаете, как подступиться?

    После прохождения видеокурса Вы освоите базовые моменты работы с JavaScript.

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

    *Наведите курсор мыши для приостановки прокрутки.

    Обработка ошибок в PHP (класс Exception)

    Перед изучением данной статьи вы можете прочитать предыдущую статью из этой серии — «Позднее статическое связывание: ключевое слово static».

    Иногда всё идет не так, как надо. Файлы где-то потерялись, серверы баз данных остались не инициализированы, URL-адреса изменились, XML-файлы повреждены, права доступа настроены неправильно, лимиты на дисковую память превышены и бог знает что еще.

    В стремлении предусмотреть любую проблему, простой метод может иногда утонуть под тяжестью собственного кода обработки ошибок.

    Ниже приведено определение простого класса Conf, который сохраняет, извлекает и определяет данные в XML-файле конфигурации.

    В классе Conf для доступа к парам «имя — значение» используется расширение PHP SimpleXml. Ниже приведен фрагмент файла конфигурации в формате XML, с которым работает наш класс.

    Конструктору класса Conf передается имя файла конфигурации, которое далее передается функции simplexml_load_file(). Полученный от функции объект типа SimpleXmlElement сохраняется в свойстве $xml.

    В методе get() для нахождения элемента item с заданные атрибутом name используется метод xpath объекта SimpleXmlElement. Значение найденного элемента возвращается в вызывающий код. Метод set() либо меняет значение существующего элемента, либо создает новый. И, наконец, метод write() сохраняет данные о новой конфигурации в исходном файле на диске.


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

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

    1. Мы можем завершить выполнение программы. Это простой, но радикальный выход. В результате наш скромный класс будет виноват в том, что из-за него потерпел неудачу весь сценарий. Хотя такие методы, как __construct() и write(), удачно расположены в коде с целью обнаружения ошибок, у них нет информации, позволяющей решить, как обрабатывать эти ошибки.

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

    Во многих PEAR-пакетах сочетаются эти два подхода и возвращается объект ошибок (экземпляр класса PEAR_Error). Наличие этого объекта говорит о том, что произошла ошибка, а подробная информация о ней содержится в самом объекте.

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

    Проблема заключается в том, что возвращаемое значение может быть затерто. В PHP нет средств, заставляющих возвращать унифицированное значение.

    На момент написания этой статьи в PHP не поддерживались уточнения типа для возвращаемого класса, поэтому ничто не может нам помешать вернуть признак ошибки вместо ожидаемого объекта или значения элементарного типа.

    Делая так, мы должны полагаться на то, что клиентский код будет проверять тип возвращаемого объекта после каждого вызова нашего метода, подверженного ошибкам. А это довольно рискованно. Никому нельзя доверять!

    Когда в вызывающий код возвращается ошибочное значение, нет никакой гарантии, что клиентский код будет «вооружен» лучше нашего метода и сможет решить, как обрабатывать ошибки. А если не сможет, то проблемы будут появляться снова и снова.

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

    С PHP 5 было введено понятие исключений, представляющих собой совершенно другой способ обработки ошибок. Я хочу сказать — совершенно другой для PHP. Но если у вас есть опыт работы с Java или C++, то исключения покажутся вам знакомыми и близкими. Использование исключений позволяет решить все проблемы, о которых мы говорили ранее.

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

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

    Общедоступные методы класса Exception

    getMessage() — получить строку сообщения, переданную конструктору;
    getCode() — получить код ошибки (целое число), который был передан конструктору;
    getFile() — получить имя файла, в котором было сгенерировано исключение;
    getLine() — получить номер строки, в которой было сгенерировано исключение;
    getPrevious() — получить вложенный объект типа Exception;
    getTrace() — получить многомерный массив, отслеживающий вызовы метода, которые привели к исключению, включая имя метода, класса, файла и значение аргумента;
    getTraceAsString() — получить строковую версию данных, возвращенных методом getTrace();
    __toString() — вызывается автоматически, когда объект Exception используется в контексте строки. Возвращает строку, описывающую подробности исключения.

    Класс Exception крайне полезен для поиска сообщения об ошибке и информации для отладки (в этом отношении особенно полезны методы getTrace() и getTraceAsString()). На самом деле класс Exception почти идентичен классу PEAR_Error, который мы обсуждали выше. Но в нем сохраняется меньше информации об исключениях, чем есть на самом деле.

    Совместно с объектом Exception используется ключевое слово throw. Оно останавливает выполнение текущего метода и передает ответственность за обработку ошибок назад в вызывающий код. Давайте подкорректируем метод __construct(), чтобы использовать оператор throw.

    Аналогичная конструкция может использоваться и в методе write().

    Теперь наши методы __construct() и write() могут тщательно проверять ошибки в файле по мере выполнения своей работы. Однако при этом решение о том, как реагировать на любые ошибки, будет приниматься в клиентском коде.

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

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

    Как видите, оператор catch внешне напоминает объявление метода. Когда генерируется исключение, управление передается оператору catch в контексте вызывающего метода, которому автоматически передается в качестве переменной-аргумента объект типа Exception.

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

    Создание подклассов класса Exception

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

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

    В сущности, вы можете определить столько операторов catch, сколько нужно для одного оператора try. То, какой конкретно оператор catch будет вызван, будет зависеть от типа сгенерированного исключения и указанного уточнения типа класса в списке аргументов. Давайте определим некоторые простые классы, расширяющие класс Exception.

    Объект типа LibXmlError создается автоматически, когда средства SimpleXml обнаруживают поврежденный XML-файл. У него есть свойства message и code, и он напоминает класс Exception. Мы пользуется преимуществом этого подобия и используем объект LibXmlError в классе XmlException.

    У классов FileException и ConfException не больше функциональных возможностей, чем у родительского класса Exception. Теперь мы можем использовать эти классы в коде и подкорректировать оба метода __construct() и write().

    Метод __construct() генерирует исключение типа XmlException, FileException или ConfException, в зависимости от вида ошибки, которую он обнаружит.

    Обратите внимание на то, что методу simplexml_load_file() передается флаг LIBXML_NOERROR. Это блокирует выдачу предупреждений внутри класса и оставляет программисту свободу действий для их последующей обработки с помощью класса XmlException.

    Если обнаружится поврежденный XML-файл, то метод simplexml_load_file() уже не возвращает объект типа SimpleXmlElement. Благодаря классу XmlException в клиентском коде можно будет легко узнать причину ошибки, а с помощью метода libxml_get_last_error() — все подробности этой ошибки.

    Метод write() генерирует исключение типа FileException, если свойство $file указывает на файл, недоступный для записи.

    Итак, мы установили, что метод __construct() может генерировать одно из трех возможных исключений. Как мы можем этим воспользоваться? Ниже приведен пример кода, в котором создается экземпляр объекта Conf().

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

    Например, если бы вы разместили оператор catch для обработки исключения типа Exception перед операторами для обработки исключений типа XmlException и ConfException, ни один из них никогда бы не был вызван. Причина в том, что оба исключения относятся к типу Exception и поэтому будут соответствовать первому оператору.

    Первый оператор catch (FileException) вызывается, если есть проблема с файлом конфигурации (если этот файл не существует или в него нельзя ничего записать).

    Второй оператор catch (XmlException) вызвается, если происходит ошибка при синтаксическом анализе XML-файла (например, если какой-то элемент не закрыт).

    Третий оператор catch (ConfException) вызывается, если корректный в плане формата файл XML не содержит ожидаемый корневой элемент conf.

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

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

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

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

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

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

    Метод write() в нашем примере «знает», когда попытка сделать запись заканчивается неудачей и почему, но «не знает», что с этим делать. Именно так и должно быть.

    Если бы мы сделали класс Conf более «сведущим», чем он есть в настоящее время, он бы потерял свою универсальность и перестал бы быть повторно используемым.

    На этом пока всё. В следующем материале мы поговорим о ключевом слове final и о том, как предотвратить наследование в PHP (скоро будет доступен).

    Понравился материал и хотите отблагодарить?
    Просто поделитесь с друзьями и коллегами!

    Добавить комментарий