Ответ 1
Вы должны расширить класс 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 позаботится об остальном.
- Неспособность передать аргумент
Итак, вы видите силу, которую дает ООП + Исключения. Если у вас есть экземпляр объекта определенного типа, вы можете быть на 100% уверены, что его данные действительны. Это огромный шаг вперед от передачи массивов данных в любом комплексном приложении.
Теперь вышеуказанный __construct
может выйти из строя из-за двух проблем: имя пользователя слишком короткое или база данных по какой-либо причине не работает. Объект PDO
действителен, поэтому соединение работало во время создания объекта, но, возможно, оно снизилось тем временем. В этом случае вызов $db->save
будет вызывать собственный PDOException
или его подтип.
try {
$user = new User($_POST['username'], $db);
} catch (InvalidArgumentException $e) {
echo $e->getMessage();
}
Поэтому я бы использовал приведенный выше код для создания объекта User
. Я не проверял заранее, будет ли имя пользователя длиной не менее 3 символов, потому что это нарушит принцип DRY. Вместо этого я просто позволю конструктору беспокоиться об этом. Если сбой конструкции с InvalidArgumentException
, я знаю, что имя пользователя неверно, поэтому я дам знать об этом пользователю.
Что делать, если база данных не работает? Тогда я не могу продолжать делать что-либо в своем текущем приложении. В этом случае я хочу полностью закрыть приложение, отображая страницу "Ошибка внутреннего сервера HTTP 500". Вот один из способов сделать это:
try {
$user = new User($_POST['username'], $db);
} catch (InvalidArgumentException $e) {
echo $e->getMessage();
} catch (PDOException $e) {
abortEverythingAndShowError500();
}
Но это плохой способ. База данных может не работать в любое время в любом месте приложения. Я не хочу делать эту проверку в любой момент, когда я передаю соединение с базой данных. Вместо этого я сделаю так, чтобы я разрешил исключение. На самом деле, он уже подпрыгнул. Исключение не было выбрано new User
, оно было вызвано вызовом вложенной функции $db->save
. Исключение уже преодолело не менее двух уровней. Поэтому я просто позволю ему путешествовать еще дальше, потому что я установил свой глобальный обработчик ошибок для работы с PDOExceptions
(он регистрирует ошибку и отображает хорошую страницу ошибок). Я не хочу беспокоиться об этой конкретной ошибке здесь. Итак, вот оно:
Использование различных типов исключений позволяет мне игнорировать определенные типы ошибок в определенных точках моего кода и позволять другим частям моего кода обрабатывать их.Если у меня есть объект определенного типа, мне никогда не придется сомневаться, проверять или беспокоиться о его действительности. Если бы это было недействительно, у меня не было бы его экземпляра в первую очередь. И, если он когда-либо недействителен (например, внезапно сбой подключения к базе данных), объект может сам сигнализировать о возникновении ошибки. Все, что мне нужно сделать, это catch
Исключение в правой точке (которое может быть очень высоким), мне не нужно проверять, что что-то преуспело или нет в каждой отдельной точке моей программы. Результат - более строгий, более структурированный код. В этом очень простом примере я использую общий InvalidArgumentException
. В несколько более сложном коде с объектами, которые принимают много аргументов, вы, вероятно, захотите провести различие между различными типами недопустимых аргументов. Следовательно, вы создадите свои собственные подклассы Exception.
Попробуйте воспроизвести это, используя только один тип исключения. Попытайтесь реплицировать это, используя только вызовы функций и return false
. Вам нужно намного больше кода, чтобы делать это каждый раз, когда вам нужно сделать эту проверку. Написание пользовательских исключений и пользовательских объектов - это немного больше кода и очевидной сложности, но это экономит вам тонну кода позже и делает вещи намного проще в долгосрочной перспективе. Поскольку все, что не должно быть (например, пользователь с слишком коротким именем пользователя), гарантированно вызывает ошибку. Вам не нужно проверять каждый раз. Напротив, вам нужно только беспокоиться о том, на каком слое вы хотите скомпоновать ошибку, а не о том, найдете ли вы ее вообще.
И это действительно не усиливает "написать свои собственные исключения":
class UserStoppedLovingUsException extends Exception { }
Там вы создали свой собственный подкласс Exception. Теперь вы можете throw
и catch
в соответствующих точках вашего кода. Вам не нужно делать ничего больше. Фактически, у вас есть формальное объявление о типах вещей, которые могут пойти не так в вашем приложении. Разве это не избивает много неофициальной документации и if
и else
и return false
s?