gzhegow
@gzhegow
aka "ОбнимиБизнесмена"

Почему, кроме того что «кто-то там уже придумал» рекомендуют под каждый эксепшен писать отдельный класс?

Тут есть сложность вопроса, но, выбрать проще чем простой или "тупой" в списке не было, извиняйте)

Я всё до конца понять не могу, почему методы стандартного PHP exception, такие ну как бы это сказать - неудобные?
Я слышал многие мнения по этому поводу, но почти все они отталкиваются от того что "люди уже придумали, тебе нужно просто делать как уже придумали".

Когда я опираюсь на эту фразу, я вспоминаю тысячи библиотек переписанных с нуля, потому что люди запилили либу, кинули в сеть и потом - там комьюнити засрало, тут он просто не знал и хотел на вакансию, здесь ни с кем не советовался, а тут просто родил, потому что устал.


standart:
throw new \HttpException500('Text'); // и array вообще нельзя
throw new \LogicException('Wtf? There is an ENGLISH string?'); // перевести? болт. array вообще нельзя
throw new \InvalidArgumentException('Wtf? There is an ENGLISH string?'); // перевести? болт. array вообще нельзя
throw new \MyLibrary\InvalidArgumentException('Wtf? There is an ENGLISH string?'); // перевести? болт. array вообще нельзя


Почему не рекомендуется вот так?
throw (new \Ex(\MyLibrary\MyClass::ERR_NOT_FOUND))->data($array);
throw (new \Http(500))->data($array);
throw (new \Flash('validation.error', '@application.err.phrase', $placeholder1, $placeholder2))->data($array);
throw (new \Invalid('login', 'rule_login', $placeholder2))->data($array);


Понимая, что само сообщение вряд ли нужно ребятам которые следят за тысячей серверов, то приходит мысль как-то так использовать стандартный эксепшен
throw new \InvalidArgumentException(json_encode((array) $data));

чтобы в принципе понять хотя бы что пришло куда ушло

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

Сначала я видел в фалконе, они там взяли вокруг программы написали один-единственный трайкетч, который выполняет функцию. Если вернулось в false - считается что поймано. Но это в любом случае не позволяет вернуться на то место где легла программа, она заканчивается, просто не критически при таком подходе. Из плюсов это дает возможность навесить несколько обработчиков а ля ивент. Из минусов - если внутри этого ивента опять произойдет эксепшен то на этот раз все ляжет. причем с фаталом.

Меня просто беспокоит, что эксепшен кидается (может быть брошен и будет брошен) методами например по итеративному обходу

foreach ($dto as $field => $value) {
  $this->processString($value); // @throws
  $this->processInt($value); // @throws
  $this->processArray($value); // @throws
}


Дядя Фаулер топящий за ООП и архитектуру приложений когда ему такое принесли слова быстро нашел. Вы говорит неправильно используете эксепшены (ну разумеется, не правы мы, но не стандарт). Тут нужно ErrorBag делать проще говоря массив с ошибками собирать.

Правильно. Только прога кидает эксепшены и ей не до массива с ошибками ваще. Если вдруг где что - критикал на пол экрана...

Что мне вчера обьяснил Иван Шумов (очень умный кстати мастер своего дела, редко таких встречаю):
1. системы логирования собирают логи по имени класса эксепшена, чтобы группировать их в неких своих админ-панелях, где в выпадающем списке по имени класса можно как-то понять, что произошло
2. в документации есть способ написать @throws и при беглом обзоре по бумагам понять что осторожнее, сие кидает ошибку и прога может рухнуть
3. немного непонятный мне пункт - другая система пришлет тебе эксепшен, который ты не готов обработать если делаешь по своему. не совсем понятно как другой скрипт даже не то что система может прислать мне эксепшен, если там куча компьютеров - то они обмениваются сообщениями через бас, а не эксепшенами

Можете объяснить мне-дурачку?

В лучшем случае конечно - как переводы делать эксепшенов без необходимости в конкретном трайкетче каждый раз писать "переведи переведи переведи", или хотя бы просто - какого черта под каждую ошибку пишут новый класс? Раньше же делали const ERR_NOT_FOUND, и её можно было хочешь return, хочешь throw, хочешь в ErrorBag - куда угодно, неймспейс не нужен был потому что ошибки лежали в интерфейсе самого класса, который их кидает.

Почему вдруг классы поперли?
  • Вопрос задан
  • 237 просмотров
Решения вопроса 1
glaphire
@glaphire Куратор тега PHP
PHP developer
То что писал Иван Шумов (п.1, 2) правильно. Классы эксепшенов всегда проще разграничить заранее, чем потом выделять в отдельный класс и везде рефакторить, когда логика проекта потребует индивидуального try-catch. Нормальные либы/пакеты/фреймворки наследуют свои эксепшены от стандартных пхпшных, вы всегда можете ловить глобальный \Exception или другой родительский, если вам не нужна такая подробная обработка на каждый случай.
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
gzhegow
@gzhegow Автор вопроса
aka "ОбнимиБизнесмена"
Похоже нашел некоторый удобный способ их применять. Опишу здесь, если кому пригодится.

Во первых там стандарт есть какой-то PSR где есть 8 классов "опасности" ошибок. Но когда я вижу классификацию на 8 типов, то выглядит это как "на всякий случай сделаем" - человек не способен делить что-то на 8 типов, он в голове держит не более 7 вещей (и это очень прокачанный - 7 вещей). Определить - какая именно из 8 типов ошибка та, которая сейчас пришла в голову для меня - невозможно. Может кто-нибудь в книжке написал, но в жизни думаю он так не делает.

Если ошибки можно поделить на 8 степеней, то речь здесь не в степенях а в приоритете. То есть \SplPriorityQueue с цифрой приоритета, который в случае чего можно всегда поменять, и посмотреть разницу в статистике за период времени. В данном случае 8 типов означают что-то вроде цветовых маркеров на производстве - "откройте красный ящик, переложите вещи в синий ящик", только без раскраски, но в общем случае 8 для однозначного определения - слишком много, а для статистики - достаточно, хотя в то же время и нет.

Делим возникающие косяки на:
1. уведомление разработчика, что программа кривая и так не должно было быть
2. уведомление/перенаправление пользователя
3. оповещение других модулей в соседних папках о том, что случилось что-то, что они могли бы решить, но в одиночку никак

В первую группу в основном падают стандартные эксепшены. Пишем мы функцию
function a() : int
{
  return 'vasia';
}

Функия возвращает не то, что просили. Об этом должен узнать разработчик, который делает на базе вашего кода. Программа не должна запуститься. Удобный способ делать такие проверки, писать следующий код:
function verifyEmail($email) : ? string
{
  if (filter_var($email, FILTER_VALIDATE_EMAIL)) return null;
  return __FUNCTION__ . ' fails: ' . $email;
}
// ... code
if ($err = verifyEmail(123)) throw new \UnexpectedValueException($err);

Мы возвращаем текст ошибки если проверка не прошла, валидируем.
Так, например, можно сделать проверку входных параметров на собственный тип не занимаясь ООП ересью на зашивание проверки в конструктор и разбивание массива на свойства, каждое из которых сеттер с валидатором и тд. минус файл и куча логики. Эти ошибки бывают что требуют вывода дополнительной информации в трейс. А поскольку эксепшен не принимает данные дополнительным параметром, я рекомендую дописать функционал `public function data(array $data))` и в особых случаях вызывать throw (new MyException($err))->data($data); и тогда в логах можно добавить эту самую $data не прибегая к танцем с бубном. Она врядли понадобится системному администратору или девопсу, но она точно понадобится тому, кто пойдёт ошибку править. Опять же - в эксепшене есть дерево вызовов и там есть аргументы. Но пару раз по причинам мне непонятным - пыха оставляет там не всё, что я ожидаю там прочитать. В той же валидации аргументом прилетает одна строка, если вывести все дерево - лог будет слишком огромный. А если одну строку - то это ничего не скажет. Чтобы понять, почему валидация сломалась часто нужно увидеть весь входной словарь данных.

Во вторую группу я отнес ошибки HTTP приложения, приложения как штуки способной принимать команды и отвечать на них. Это разные редиректы. Это 500ые коды сделанные вручную. Это отсутствие прав доступа. Всё что по-любому закончится HTTPException или RedirectBack. Программа запуститься, но ошибка может быть вызвана тем, что во внешних условиях что-то идёт не так - нет прав доступа, или этот кусок программы сейчас отключен. То есть это не косяк кода, а сознательно установленный запрет. Эти ошибки чаще всего нужно переводить на другой язык или писать заглушку. Эти ошибки как правило бросаются фреймворком или приложением, и имеют либо неймспейс как у фреймворка, либо что-нибудь вроде \App\HttpException;

В третью группу я отнёс ошибки, которые требуют внешних зависимостей. Насчет этой группы я пока не уверен, может быть это из-за того что я неправильно разобрался с первыми двумя. Предположим у нас есть модуль авторизации пользователя и есть модуль для работы с JWT-токенами. Вот когда модуль не смог разобрать JWT токен - он должен кинуть ошибку "я отработал и решил, что что-то пошло не так, но дальше - не моя забота". JWT разбор выполняет очень узкую задачу - парсинг токена. Туда не всунешь зависимостью модуль авторизации, это как поставить директора в подчинение работнику. Можно попробовать НЕ-бросать эту ошибку путем того, что инициировать проверку токена в модуле Авторизации, чтобы прямо там её и обработать. Но это странно, потому что Авторизация может быть сделана десятью путями и каждый из них зависимостью хардкодить - не очень хорошо. Именно эти ошибки должны иметь неймспейс, потому что их будут ловить и с ними работать, пытаясь их заглушить. То есть на уровне модуля - это критическая ошибка. А на уровне программы - это предупреждение.

На текущий момент мне кажется это правильным подходом. Если найду что-то ещё исправлю.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы
YCLIENTS Москва
от 200 000 до 350 000 ₽
Ведисофт Екатеринбург
от 25 000 ₽
ИТЦ Аусферр Магнитогорск
от 100 000 до 160 000 ₽
22 апр. 2024, в 16:15
18000 руб./за проект
23 апр. 2024, в 16:55
10000 руб./за проект