Все мы видели проекты, где ошибки решаются через dd(), abort() и "ну оно же работает". Это ад. Если хочешь код, который живёт дольше одного релиза, учись писать свои исключения.

Laravel сам по себе неплохо справляется с ошибками: что-то упало — он покажет трейс или красивую страницу. Но этого мало. Когда у тебя логика усложняется, throw new Exception("Что-то пошло не так") превращается в мусор, который никто не понимает.

Собственные исключения нужны, чтобы в коде сразу было ясно: что именно сдохло, где и почему. И чтобы фронт не получал загадочный "500 Internal Server Error", когда у тебя просто не нашёлся юзер.

Создаём своё исключение

Laravel даёт команду:

php artisan make:exception InvalidUserException

Она положит новый класс в app/Exceptions/.

Пример:

namespace App\Exceptions;

use Exception;

class InvalidUserException extends Exception
{
    public function render($request)
    {
        return response()->json([
            'error' => 'Invalid user',
        ], 400);
    }
}

Тут есть важный момент: render() — это хук, где ты сам решаешь, что отдавать пользователю. Не хочешь JSON — верни Blade-шаблон. Хочешь кастомный код — ставь хоть 418 I’m a teapot.

Как использовать

Просто кидаешь исключение там, где понимаешь, что дальше ехать нельзя:

$user = User::find($id);

if (! $user) {
    throw new \App\Exceptions\InvalidUserException();
}

И всё. Логика сразу ясна: мы работаем не с "какой-то ошибкой", а конкретно с ситуацией "юзер невалиден".

Где ловить

Laravel сам поймает и вызовет render(). Но если хочешь общую логику — лезь в App\Exceptions\Handler.

Пример:

public function render($request, Throwable $e)
{
    if ($e instanceof InvalidUserException) {
        return response()->json(['error' => 'Такого юзера нет'], 404);
    }

    return parent::render($request, $e);
}

Теперь у тебя глобальная обработка, и ты можешь рулить всем на одном уровне.

Где это реально спасает

API: вместо каши из "500 Error" у тебя будут конкретные JSON-ответы с кодами.
Бизнес-логика: легко отличить "не найден заказ" от "база отвалилась".
Логирование: в логах сразу видно тип исключения, а не "Exception #12345".

Где можно облажаться

— Пихать исключения вместо условий. Если у тебя нормальная ситуация, а не критическая ошибка, не надо её оборачивать в throw. Не нашёл юзера? Иногда это норм — верни пустой результат.
— Плодить исключения на каждую фигню. "InvalidPhoneNumberException", "InvalidPasswordException", "InvalidTokenLengthException" — и вот у тебя каталог из 300 классов, которые никто не использует.
— Забывать про коды. Исключение без статуса HTTP — это как мат без эмоции: вроде сказано, а смысла нет.

Финалочка

Свои исключения — это не про красоту кода, а про контроль. Ты явно указываешь, где у тебя критический сбой, и сразу решаешь, как его показать пользователю или API.