Исключения – один из немногих примеров удачного использования наследования. В этом уроке мы научимся создавать свои исключения и перехватывать их.
Обычно исключения используются так. Ближе к началу программы стоит конструкция try/catch, которая ловит исключения и показывает пользователю адекватное сообщение:
<?php
try {
doSomethingDangerous();
} catch (\Exception $e) {
echo 'Something happens';
}
Но как понять что случилось? Иногда это важно. Разные ошибки могут приводить к разному поведению программы. Кроме того, не все ошибки требуют обработки в текущем месте программы.
Разделять ошибки можно с помощью разных классов (или интерфейсов) наследующихся от класса Exception, который в свою очередь реализует интерфейс Throwable. По техническим причинам, реализовать этот интерфейс напрямую нельзя, поэтому остаётся только один путь – наследование.
<?php
namespace App;
class MyException extends \Exception {}
Причём в отличие от других примеров наследования, в исключениях редко нужно добавлять или изменять поведение. Основная цель использования наследования исключений – описание всех возможных типов ошибок.
Теперь посмотрим как этим можно воспользоваться:
<?php
try {
// Какой-то код
} catch (MyException $e) { // Проверка instanceof
// Делаем что-нибудь одно
} catch (\Exception $e) {
// Делаем что-нибудь другое
}
Конструкция try/catch работает очень похоже на switch, но только для исключений. С её помощью можно описать обработку каждого типа исключений добавив новый блок catch. Во время срабатывания исключения, PHP проверяет каждый блок catch на instanceof, начиная с верхнего. Поэтому порядок блоков catch имеет важное значение.
В самом PHP уже есть иерархия исключений, которая позволяет разделять реакцию на разные типы ошибок:
interface Throwable
|- Error implements Throwable
|- ArithmeticError extends Error
|- DivisionByZeroError extends ArithmeticError
|- AssertionError extends Error
|- ParseError extends Error
|- TypeError extends Error
|- ArgumentCountError extends TypeError
|- Exception implements Throwable
|- ClosedGeneratorException extends Exception
|- DOMException extends Exception
|- ErrorException extends Exception
|- IntlException extends Exception
|- LogicException extends Exception
|- BadFunctionCallException extends LogicException
|- BadMethodCallException extends BadFunctionCallException
|- DomainException extends LogicException
|- InvalidArgumentException extends LogicException
|- LengthException extends LogicException
|- OutOfRangeException extends LogicException
|- PharException extends Exception
|- ReflectionException extends Exception
|- RuntimeException extends Exception
|- OutOfBoundsException extends RuntimeException
|- OverflowException extends RuntimeException
|- PDOException extends RuntimeException
|- RangeException extends RuntimeException
|- UnderflowException extends RuntimeException
|- UnexpectedValueException extends RuntimeException
Перехват любого базового исключения, автоматически влечёт за собой перехват всех наследников текущего класса. Например, если в блоке catch перехватывать RuntimeException, то этот блок поймает все ошибки, входящие в иерархию RuntimeException.
В PHP есть негласное правило о том, как работать с иерархиями исключений. Любая программа, должна определять своё собственное высокоуровневое исключение, которое наследуется от RuntimeException. Все остальные исключения библиотеки наследуются от него. Такой подход позволяет изолировать обработку ошибок конкретной библиотеки буквально одним блоком catch. Например, в HTTP-клиенте Guzzle, базовое исключение TransferException. Это значит, что среди всех ошибок мы можем перехватить ошибки этого клиента:
<?php
try {
$client = new \GuzzleHttp\Client();
$response = $client->get('https://ru.hexlet.io');
} catch (Guzzle\TransferException) {
// Сюда попадут все ошибки библиотеки Guzzle
}
Блок finally
В некоторых ситуациях бывает нужно продолжить работу независимо от того, возникло исключение или нет. Используя только try/catch, эту задачу нельзя выполнить без дублирования. Придётся размещать код как после всей конструкции try/catch, так и в каждом блоке catch.
Это привело к тому, что саму конструкцию расширили, добавив в неё блок finally. Этот блок вызывается в самом конце и в любом случае:
<?php
try {
// Какой-то код
} catch (MyException $e) {
// Делаем что-нибудь одно
} finally {
// Вызовется в любом случае
}
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар «Как самостоятельно учиться»
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.