Вы должны расширить класс Exception своими собственными типами Exception, если вам необходимо различать различные типы ошибок. Бросок Exception
просто означает что-то пошло не так . Вы не знаете, , что пошло не так, хотя Стоит ли все прерывать? Это ожидаемая ошибка? Бросок UserIsNotAllowedToDoThisException
означает нечто гораздо более конкретное. Важно различать, какой код может обрабатывать какие ошибки:
try {
new Foo($bar);
} catch (UserIsNotAllowedToDoThisException $e) {
echo "Sorry, you're not allowed to do this.";
}
Этот код обрабатывает простой случай, когда что-то не разрешено. Если Foo
вызовет какое-то другое исключение, например TheDatabaseJustCrashedAndIsBurningException
, вы не хотите знать об этом здесь, вы хотите, чтобы какой-то глобальный обработчик ошибок обрабатывал его. Различая , что пошло не так, это позволяет вам правильно решать проблемы.
Хорошо, вот немного более полный пример:
Во-первых, если вы используете правильный ООП, вам нужно Исключения для сбоя конструкций объекта. Не имея возможности провалить конструкцию объекта, вы игнорируете большую часть ООП: безопасность типов и, следовательно, целостность данных. См. Например:
class User {
private $name = null;
private $db = null;
public function __construct($name, PDO $db) {
if (strlen($name) < 3) {
throw new InvalidArgumentException('Username too short');
}
$this->name = $name;
$this->db = $db;
$this->db->save($this->name); // very fictional DB call, mind you
}
}
В этом примере мы видим много вещей:
- Мои
User
объекты имеют , чтобы иметь имя. Если не передать аргумент $name
конструктору, PHP потерпит неудачу во всей программе.
- Имя пользователя должно содержать длиной не менее 3 символов. Если это не так, объект не может быть построен (потому что выброшено исключение).
- Мои
User
объекты имеют , чтобы иметь действующее и работающее соединение с базой данных.
- Если не передать аргумент
$db
, PHP потерпит неудачу во всей программе.
- Если не передать действительный экземпляр
PDO
, PHP не сможет выполнить всю программу.
- Я не могу передать только что-нибудь в качестве второго аргумента, это должен быть действительный объект PDO.
- Это означает , если создание экземпляра
PDO
завершилось успешно, у меня есть действующее соединение с базой данных. Мне не нужно беспокоиться или проверять действительность моего соединения с базой данных впредь. По этой же причине я создаю объект User
; если конструкция завершается успешно, у меня есть действительный пользователь (допустимый, что означает, что его имя длиной не менее 3 символов). Мне не нужно проверять это снова. Когда-либо. Мне нужно набрать подсказку только для User
объектов, PHP позаботится обо всем остальном.
Итак, вы видите силу, которую дает вам OOP + Exceptions. Если у вас есть экземпляр объекта определенного типа, вы можете быть на 100% уверены, что его данные действительны. Это огромный шаг по сравнению с передачей массивов данных в любом наполовину сложном приложении.
Теперь вышеприведенный __construct
может завершиться ошибкой из-за двух проблем: слишком короткое имя пользователя или база данных по какой-либо причине не работает . Объект PDO
действителен, поэтому соединение работало в то время, когда объект был создан, но, может быть, оно тем временем оборвалось. В этом случае вызов $db->save
выдаст свой PDOException
или его подтип.
try {
$user = new User($_POST['username'], $db);
} catch (InvalidArgumentException $e) {
echo $e->getMessage();
}
Так что я бы использовал приведенный выше код для создания User
объекта. Я не проверяю заранее, является ли имя пользователя длиной не менее 3 символов, потому что это нарушило бы принцип СУХОЙ. Вместо этого я просто позволю конструктору беспокоиться об этом. Если конструкция не удалась с InvalidArgumentException
, я знаю, что имя пользователя было неверным, поэтому я сообщу пользователю об этом.
Что если база данных не работает? Тогда я не могу продолжать делать что-нибудь в моем текущем приложении. В этом случае я хочу полностью остановить свое приложение, отображая страницу HTTP 500 Internal Server Error. Вот один способ сделать это:
try {
$user = new User($_POST['username'], $db);
} catch (InvalidArgumentException $e) {
echo $e->getMessage();
} catch (PDOException $e) {
abortEverythingAndShowError500();
}
Но это плохой способ. База данных может выйти из строя в любое время в любом месте приложения. Я не хочу делать эту проверку в любой момент, когда я передаю соединение с базой данных чему-либо. Вместо этого я позволю исключению всплыть . На самом деле, это уже всплыло. new User
исключение не было сгенерировано, оно было вызвано вызовом вложенной функции в $db->save
. Исключение уже прошло как минимум два слоя. Так что я просто позволю этому продвинуться еще дальше, потому что Я настроил свой глобальный обработчик ошибок для обработки PDOExceptions
(он регистрирует ошибку и отображает красивую страницу с ошибкой). Я не хочу беспокоиться об этой конкретной ошибке здесь. Итак, вот оно:
Использование различных типов исключений позволяет мне игнорировать определенные типы ошибок в определенных точках моего кода и позволяет другим частям моего кода обрабатывать их. Если у меня есть объект определенного типа, я делаю никогда не нужно ставить под сомнение или проверять или беспокоиться о его действительности. Если бы это не было действительным, у меня не было бы экземпляра этого во-первых. И, если когда-либо не сможет быть действительным (как внезапно оборвавшееся соединение с базой данных), объект может сам сигнализировать, что произошла ошибка . Все, что мне нужно сделать, это catch
Исключение в нужной точке (которая может быть очень высокой), я не должен проверить, удалось ли что-то или нет в каждой отдельной точке моей программы. Результат - меньше, более надежный, лучше структурированный код . В этом очень простом примере я использую только общий InvalidArgumentException
. В несколько более сложном коде с объектами, которые принимают много аргументов, вы, вероятно, захотите провести различие между разными типами недопустимых аргументов. Следовательно, вы создадите свои собственные подклассы исключений.
Попробуйте воспроизвести это, используя только один тип исключения. Попробуйте повторить это, используя только вызовы функций и return false
. Вам нужно намного больше кода , чтобы сделать это каждый раз вам нужно сделать эту проверку. Написание пользовательских исключений и пользовательских объектов требует немного больше кода и очевидной сложности заранее, но это сэкономит вам массу кода позже и сделает вещи на 1132 * намного проще в долгосрочной перспективе. Потому что все, что не должно быть (как у пользователя с слишком коротким именем пользователя), гарантированно вызовет какую-то ошибку. Вам не нужно проверять каждый раз. Напротив, вам нужно беспокоиться только о том, на каком слое вы хотите содержать ошибку, а не о том, найдете ли вы ее вообще.
И действительно, не нужно «писать свои собственные исключения»:
class UserStoppedLovingUsException extends Exception { }
Там вы создали свой собственный подкласс исключений. Теперь вы можете throw
и catch
в соответствующих точках вашего кода. Вам не нужно делать больше, чем это. Фактически, теперь у вас есть формальное объявление типов вещей, которые могут пойти не так в вашем приложении. Разве это не бьет много неофициальной документации и if
с и else
с и return false
с?