Главная вещь, которую надо понимать про исключения, это то что они бывают
двух основных видов.
После этого вся обработка становится совершенно естественной и очевидной.
- Error exceptions, или по простому говоря - ошибки. Обычные ошибки при выполнении программы. Обычно код бросает их сам. Решение "обрабатывать все ошибки через set_exception_handler" будет вполне логичным.
- Business logic exceptions - это не ошибка в строгом понимании этого слова, а скорее нормальное поведение программы. Ситуация исключительная, но только для бизнес-логики. Их всегда кидает программист.
И вот просто тупо исходя из того что их два типа, уже можно сказать что единого ответа "где лучше" не существует.
У каждого типа своя логика обработки. Но при этом, как только ты уложил в голове различия, эта логика становится совершенно очевидной:
- Error exceptions почти никогда не ловятся через try-catch, по крайней мере на месте. За исключением редких исключительных ситуаций обработка ошибок производится в единой точке, обработчике ошибок
- Business logic exceptions всегда ловятся через try-catch
Отсюда мы видим, что
- //PDOException при коннекте (эммм... я понимаю что пример, но блин, new PDO в конструкторе репы, серьёзно?ладно, мы сейчас не об этом) - это однозначно ошибка
- //PDOException в запросе - это тоже ошибка, тут два раза думать не надо
- условно пустое имя. Ну вот здесь мы уже переходим в область бизнес-логики. Коду тут без разницы, пустое имя, или полное. Это важно нам - программисту, пользователю.
Но тут есть один, блин, тонкий момент.
Валидация, по сути, пытается разорваться между всеми слоями приложения.
С одной стороны, это функция Сущности (которую ошибочно называют моделью) - проверять валидность своих данных.
С другой - если нам надо донести результаты валидации до пользователя, то как быть с переводами? Тащить в модель переводчик, серьёзно? Ну ок, ладно, возвращаем ключи для перевода. Хотя тоже как-то...
Но вот проверка емейла на уникальность. Её-то где делать?
В Сущности? И тащить в нее соединение с БД?
На уровне БД? А где ловить тогда исключение? В сервисе? И ломиться через несколько уровней абстракции к сырому PDOException? Не вариант.
Или, к примеру, для модели естественно проверять каждое поле отдельно, и кидать исключение. А для пользовательского интерфейса это неприемлемо - надо выдавать все ошибки валидации скопом, а не скармливать по одной.
Вопросы...
Но "где валидировать данные" - это отдельная тема, которая не относится напрямую к вопросу "где ловить ошибки".
В данном случае я предлагаю оставить Сущность Юзер без валидации, а всю валидацию делать в сервисе.
Хотя опять же - в современных фреймворках валидацию (Не будем показывать пальцем, но это был Ларавель) вообще делают еще до запуска контроллера, в миддлвари. Это кстати спорное решение, которое нарушает целостность модели. Если мы обращаемся к модели через другую точку входа, не контроллер, а, к примеру, создаем юзера через командную строку, то нам нужна точно такая же валидация. Запускать команды через мидлварь? В сущности, это мысль... Но всё равно, мы в итоге бизнес-логику размазываем между моделью и точками входа в неё, а это костыль.
И при всём при этом оставлять Сущность Юзер совсем без валидации тоже как-то не комильфо... А если оставлять - то получится по сути дублирование кода.
Вопросы, вопросы...
Но вернемся к нашим баранам, в смысле юзерам.
Начнем с того, что проверка имени на равенство пустой строке или нулю - это какой-то детский лепет (и кстати, почему ноль нельзя? вот у Маска ребеночка зовут X Æ A-12 - почему у кого-то не может быть имя "0.0"?).
Отдельно побурчу насчет empty. Вообще, это один из самых сложных операторов, на нем спотыкаются все поголовно. В частности,
function f($name){
if (empty($name))...
- это бессмыслица. Звучит, в переводе на русский, немного шизофренически: "пусть у нас будет переменная $name. Если у нас нет переменной $name...". Ну как нет, если мы только что ее в функцию передали?
empty() проверяет переменную на существование И "пустоту". И в данном случае первая проверка будет бессмысленной. Никогда не надо писать бессмысленный код.
Поэтому логичнее будет написать просто if(!$name)
. Хотя по нынешним временам это тоже говнокод. Что мы имеем здесь в виду? Имя не может быть пустой строкой? Пустым массивом? Нулём? null? false? А true или заполненный массив - это, получается, хорошее, годное имя?
Лучше все-таки четче определять свои претензии. К примеру, проверять длину строки.
У имени можно сделать миллион проверок: Минимальную длину, максимальную длину. Проверить что это
строка, а не массив, в конце концов, и не булево значение.
И так по каждому полю.
То есть, по-хорошему,
валидация данных на соответствие правилам бизнес-логики - это отдельный большой бизнес!
При этом, по итогам валидации, исключение кидается строго одно, общее для всех ошибок.
Которое мы и ловим в контроллере через трай.
class ValidationError extends Exception{ ... };
class User
{
private string $name;
public function __construct(string $name)
{
$this->name = $name;
}
}
class UserRepository
{
public function __construct(PDO $pdo)
{
$this->builder = $pdo;
}
public function add(User $user): User
{
$saved = $this->builder->query('INSERT INTO users (name) VALUES ("user")'); //PDOException
}
}
class UserSerivce
{
private UserRepository $repository;
private Validator $validator;
public function new(array $data): User
{
$rules = [...] ;
$errors = $this->validator->validate($data, $rules);
if ($errors) {
throw new ValidationException("", $errors);
}
$user = new User($data['name']); // в принципе, сучность может здесь бросить своё ValidationException
return $this->repository->add($user);
}
}
class UserController
{
private UserSerivce $service;
public function store(array $data)
{
try {
$user = $this->service->new($data);
catch (ValidationError $e) {
// рассказываем юзеру что он дурак
}
return redirect()->to('/' . $user->id);
}
}
Но опять же, в современных фреймворках вручную этот catch не пишут, там уже при вызове контроллера мы либо в роутере, либо в миддлвари, либо еще где у черта в ступе - но, главное,
в одном месте, чтобы не писать одно и то же каждый раз, ловим определенные исключения, скажем исключения которые потом сконвертируются в 4хх ошибки НТТР. И вот ошибки валидации тоже можем там ловить.
Как можно заметить, вот весь этот длинный и путанный текст посвящен исключительно ошибкам бизнес-логики.
Поскольку с ошибками кода всё куда проще - единый хендлер тупо их обрабатывает в одном месте, как описано в статье по ссылке @Spartak-2205
За исключением редких случаев, когда они ловятся по месту. Когда ошибка некритичная, или есть сценарий обработки - например, попробовать выполнить то же действие еще раз.