Мета - щоб код, який використовує модуль, міг зловити саме те, що вміє обробити, і нічого зайвого.
Типова структура:
interface PaymentException extends Throwable {}
final class CardDeclined extends RuntimeException implements PaymentException
{
public static function forReason(string $reason): self
{
return new self("Картку відхилено: {$reason}");
}
}
final class GatewayUnavailable extends RuntimeException implements PaymentException {}
Принципи:
- Маркерний інтерфейс модуля (
PaymentException) дозволяє зловити все з модуля однимcatch, не прив'язуючись до ієрархії класів. - Успадкування від SPL-винятків (
RuntimeException,InvalidArgumentException,LogicException) зберігає зміст:LogicException- помилка програміста,RuntimeException- обставини під час виконання. - Іменовані конструктори (
forReason()) тримають формулювання повідомлень в одному місці. - Контекст як властивості, а не лише в тексті:
$e->orderIdможна записати в лог чи показати користувачу без розбору рядка. - Ланцюжок причин: загортаючи чужий виняток, передавайте його як
previous- інакше в логах загубиться справжня причина.
Чого уникати: винятків для звичайного керування потоком (користувач не знайдений - часто це null, а не виняток) і одного «універсального» AppException на все.