Exceptions в PHP

Работа с исключениями в PHP

Сегодня мы с вами разберем такую тему как исключения в PHP. Но прежде чем перейти к деталям, давайте дадим простое понятие термину «Исключительная ситуация». Исключительная ситуация в программе – это ситуация, при которой дальнейшее выполнение кода не имеет смысла. Например, новости на страничке /post/add могут добавлять только администраторы, но при этом это пытается сделать неавторизованный пользователь. Здесь имеет смысл проверить в самом начале права пользователя и если их недостаточно, обработать эту исключительную ситуацию. Или вот ещё один пример: наше приложение работает с базой данных, но при подключении к серверу MySQL обнаруживается, что такой базы данных на сервере нет. При такой ситуации тоже не имеет смысла продолжать выполнение скрипта – это исключительная ситуация.

В PHP для работы с такими исключительными ситуациями есть специальный механизм - исключения. Исключение – это такой объект специального класса. Этот класс является встроенным в PHP и называется Exception. Создание исключения выглядит следующим образом:

Код доступен только после покупки курса ООП в PHP7.

В качестве аргументов в конструктор можно передать сообщение и код ошибки:

Код доступен только после покупки курса ООП в PHP7.

При этом класс Exception это немного нестандартный класс – объекты этого класса можно «бросать». Для этого используется оператор throw. Делается это вот так:

Код доступен только после покупки курса ООП в PHP7.

Если мы сейчас запустим этот скрипт, то увидим следующее:
Непойманное стандартное исключение

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

Для того, чтобы исключение поймать, используется специальная конструкция try-catch:

Код доступен только после покупки курса ООП в PHP7.

Результат будет следующим:
Обработанное стандартное исключение

В чем же смысл такой сложной конструкции? Дело в том, что исключение будет подниматься по стеку вызовов выше и выше, до тех пор, пока оно не будет поймано. Вот что это значит:
Пусть у нас есть 3 финкции: func1, func2 и func3. func1 вызывает внутри себя func2, а func2 вызывает func3.

Код доступен только после покупки курса ООП в PHP7.

Для того, чтобы обработать это исключение уровнем выше, достаточно написать блок try-catch на уровне func2, обернув вызов func3 внутри секции try:

Код доступен только после покупки курса ООП в PHP7.

Исключение, пойманное на другом уровне

А можем и вовсе поймать его и в func1:

Код доступен только после покупки курса ООП в PHP7.

Результат будет таким же:
То же исключение

В этом случае исключение будет брошено внутри func3, поднимется на уровень func2, там его никто не поймает, и оно пойдет на еще уровень выше, в место, где была вызвана func2 – внутри func1. И вот здесь-то оно и будет поймано и обработано. После того как исключение обработано, будет выполнен код, который идет после блока try-catch.

Код доступен только после покупки курса ООП в PHP7.

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

Наследование классов-исключений

От класса Exception можно наследоваться. Таким образом, мы можем создавать свои классы исключений для разных ситуаций. Например, для ошибок при работе с базой данных мы можем создать класс DbException, а для ошибок при работе с файлами – FileSystemException.

Блок try-catch позволяет обрабатывать разные типы исключений, это выглядит так:

Код доступен только после покупки курса ООП в PHP7.

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

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

src/settings.php

Код доступен только после покупки курса ООП в PHP7.

PDOException не поймано

Как видим, возникла ошибка – непойманное исключение типа PDOException. Это такой встроенный в PHP класс для исключений при работе с базой данных через PDO.

Давайте теперь создадим собственный класс исключений специально для базы данных. Назовём его DbException.

src/MyProject/Exceptions/DbException.php

Код доступен только после покупки курса ООП в PHP7.

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

src/MyProject/Services/Db.php

Код доступен только после покупки курса ООП в PHP7.

Снова перезагрузим страничку и увидим уже наше сообщение об ошибке.

Давайте теперь обработаем наше исключение. Для этого нам требуется поймать исключение уже с типом DbException. Так как ошибка при работе с базой данных – это критичная ошибка, которая наверняка не позволит выполняться программе дальше, нам стоит ловить её на самом низком уровне нашего приложения – во фронт-контроллере. Обернем в блок try-catch код фронт-контроллера.

www/index.php

Код доступен только после покупки курса ООП в PHP7.

Если мы теперь обновим страничку, то увидим уже только наше сообщение, без каких-либо необработанных исключений. Мы поймали исключение и просто вывели текст ошибки через echo.
А теперь давайте во фронт-контроллере научимся выводить ошибки, используя наш компонент View. То есть мы будем выводить ошибки через шаблоны. Это позволит избежать дублирования кода и всегда использовать один механизм для вывода.

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

templates/errors/500.php

Код доступен только после покупки курса ООП в PHP7.

А теперь просто обработаем исключение во фронт-контроллере по-новому.

www/index.php

Код доступен только после покупки курса ООП в PHP7.

Перезагружаем страничку.
Обработанная ошибка работы с БД

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

Исключение не брошено - всё работает

А теперь давайте посмотрим на вот этот кусок кода во фронт-контроллере:

Код доступен только после покупки курса ООП в PHP7.

Этот код работает, когда не нужен нужный роутинг. Это ведь тоже исключительная ситуация! Давайте сделаем исключение для случаев, когда страничка не найдена.

src/MyProject/Exceptions/NotFoundException.php

Код доступен только после покупки курса ООП в PHP7.

И начнём бросать его в случае, когда роут не найден. Для этого заменяем код

www/index.php

Код доступен только после покупки курса ООП в PHP7.

на

Код доступен только после покупки курса ООП в PHP7.

И проверяем на несуществующем роуте, например:
Исключение о несуществующей страничке

Видим ошибку о непойманном исключении, так давайте же его поймаем! Для этого добавляем еще один блок catch.

www/index.php

Код доступен только после покупки курса ООП в PHP7.

Пробуем снова зайти по несуществующему адресу:
Корректная 404 страничка

Теперь мы видим ошибку из шаблона, и при этом сервер вернул код 404 – то, что нужно!

А теперь – внимание. Помните, мы уже использовали шаблон ошибки 404? Мы тогда писали в контроллере статей что-то типа такого:

src/MyProject/Controllers/ArticlesController.php

Код доступен только после покупки курса ООП в PHP7.

А теперь мы можем просто кинуть там исключение, вот так:

Код доступен только после покупки курса ООП в PHP7.

И оно всплывет через все слои нашей программы до фронт-контроллера, где будет успешно поймано!

Давайте перепишем контроллер статей:

src/MyProject/Controllers/ArticlesController.php

Код доступен только после покупки курса ООП в PHP7.

А теперь попробуем зайти на адрес с несуществующей статьёй: http://myproject.loc/articles/123
Ненайденная страничка статьи

Видим всё ту же ошибку! Давайте теперь посмотрим, что произошло. Поставим breakpoint в отладчике на место, где бросается исключение и обновим страничку.
Бросаем исключение и наблюдаем

Видите, слева внизу есть стек вызовов. Если переключиться на предыдущий уровень (index.php), мы увидим, где был вызван экшн контроллера:
Смотрим место где было брошено исключение

Это произошло в файле index.php, внутри блока try-catch. Так как в самом экшене брошенное исключение никто не ловит, оно всплывет на уровень, где был вызван экшен – в файл index.php. Если мы сейчас нажмем F8, то увидим, что исключение было успешно поймано и мы попали в нужный нам блок catch.

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

Здесь мы вывели сообщение об ошибке – вот и всё! Теперь, если нам в приложении потребуется где-то вывести сообщение о несуществующей странице – мы просто бросим исключение с нужным типом.

На этом всё, за домашку =)

Домашнее задание
Самостоятельно прочитайте в официальной документации о новом типе ошибок в PHP 7 – Error. Их тоже можно бросать, однако на данном этапе мы их использовать не будем и пока ограничимся только типом Exception. Также изучите самостоятельно интерфейс Throwable. Попробуйте реализовать этот интерфейс в каком-нибудь классе.

Читайте также
Комментарии


g--nokoder
g--nokoder

что же дальше? будут Composer? и хочется делать и решать реальные задачки? вроде как писать блог? это возможно?

ivashkevich
ivashkevich

Да, всё это будет в скором времени.