Глава 2.7: Обработка ошибок
Время изучения: 40 минут
1. Почему стандартные ошибки — это плохо?
Если в вашем Laravel-приложении происходит ошибка (например, не найдена запись в базе), а вы это никак не обработали, пользователь увидит огромную HTML-страницу с отладочной информацией или неинформативное сообщение "Server Error".
Для API это катастрофа. Ваше фронтенд-приложение ожидает получить JSON, а не HTML. Наша задача — перехватить любую ошибку и превратить ее в структурированный JSON-ответ.
2. Центральный диспетчер ошибок: bootstrap/app.php
В старых версиях Laravel был громоздкий файл App\Exceptions\Handler.php
. В Laravel 11/12 все стало гораздо проще и элегантнее. Центр управления ошибками теперь находится прямо в файле конфигурации вашего приложения — bootstrap/app.php
.
Откройте bootstrap/app.php
. В самом низу вы увидите блок .withExceptions(...)
. Это и есть наш "центральный диспетчер".
<?php
// bootstrap/app.php
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(function (Middleware $middleware) {
// ...
})
->withExceptions(function (Exceptions $exceptions) {
// <-- ВОТ ЗДЕСЬ МЫ БУДЕМ РАБОТАТЬ
})->create();
3. Обрабатываем самую частую ошибку: "Не найдено" (404)
Самая распространенная ошибка в API — это когда пользователь запрашивает ресурс, которого не существует (например, GET /api/planets/999
). Laravel в этом случае генерирует исключение ModelNotFoundException
или NotFoundHttpException
. Давайте их перехватим.
Добавьте следующий код внутрь .withExceptions(...)
:
<?php
// bootstrap/app.php
->withExceptions(function (Exceptions $exceptions) {
// Перехватываем исключение, когда модель не найдена в базе данных
$exceptions->render(function (ModelNotFoundException $e, Request $request) {
// Проверяем, что запрос пришел именно на наш API
if ($request->is('api/*')) {
return response()->json([
'message' => 'Запрашиваемый ресурс не найден в нашей галактике.'
], 404);
}
});
// Перехватываем исключение, когда сам маршрут не найден
$exceptions->render(function (NotFoundHttpException $e, Request $request) {
if ($request->is('api/*')) {
return response()->json([
'message' => 'Такого космического маршрута не существует.'
], 404);
}
});
})->create();
$exceptions->render(...)
— мы регистрируем "обработчик". Он говорит: "Если произойдет исключение типаModelNotFoundException
, выполни этот код".if ($request->is('api/*'))
— это важная проверка. Она гарантирует, что наш красивый JSON-ответ будет отправляться только для API-запросов, не затрагивая обычные веб-страницы.return response()->json(...)
— мы создаем и возвращаем стандартизированный JSON-ответ с кодом 404.
Теперь, если вы запросите несуществующую планету, вместо уродливой HTML-страницы вы получите аккуратный JSON.
4. Кастомные исключения: Создаем собственные "сигналы тревоги"
Иногда стандартных исключений недостаточно. Представим, что у нас есть бизнес-правило: "нельзя удалять планету 'Земля'". Если кто-то попытается это сделать, мы должны вернуть осмысленную ошибку.
Шаг 1: Создаем свой класс исключения Выполним в терминале:
Шаг 2: Используем его в контроллере
Откроем PlanetController.php
и изменим метод destroy
:
<?php
// app/Http/Controllers/PlanetController.php
use App\Exceptions\CannotDeleteEarthException; // <-- Импортируем наше исключение
use App\Models\Planet;
public function destroy(Planet $planet)
{
// Наше новое бизнес-правило
if (strtolower($planet->name) === 'земля') {
throw new CannotDeleteEarthException('Удаление планеты Земля запрещено Галактическим Кодексом.');
}
$planet->delete();
return response()->json(null, 204);
}
DELETE /api/planets/1
(где 1 — это ID Земли), наш код выбросит исключение CannotDeleteEarthException
.
Шаг 3: Учим Laravel красиво обрабатывать нашу "тревогу"
Вернемся в bootstrap/app.php
и добавим новый обработчик для нашего исключения.
<?php
// bootstrap/app.php
->withExceptions(function (Exceptions $exceptions) {
// Наш новый обработчик
$exceptions->render(function (CannotDeleteEarthException $e, Request $request) {
return response()->json([
'message' => 'Операция запрещена.',
'details' => $e->getMessage() // Получаем сообщение, которое мы передали в throw
], 403); // 403 Forbidden - "Доступ запрещен"
});
// ... (остальные обработчики для 404)
})->create();
5. Обработка всех остальных сбоев (500 Internal Server Error)
Что делать со всеми остальными, непредвиденными ошибками? Например, если отвалилась база данных или в коде синтаксическая ошибка. Для этого мы можем зарегистрировать "универсальный" обработчик для самого общего типа ошибок — Throwable
.
Важно: Этот обработчик должен быть последним, чтобы не перехватывать более специфичные исключения, которые мы определили выше.
<?php
// bootstrap/app.php
->withExceptions(function (Exceptions $exceptions) {
// ... (обработчики для CannotDeleteEarthException и 404)
// УНИВЕРСАЛЬНЫЙ ОБРАБОТЧИК (в самом конце)
$exceptions->render(function (Throwable $e, Request $request) {
if ($request->is('api/*')) {
// В режиме отладки можно показать настоящее сообщение об ошибке
$message = config('app.debug')
? 'Произошла ошибка: ' . $e->getMessage()
: 'На борту произошла непредвиденная ошибка. Инженеры уже вызваны.';
return response()->json(['message' => $message], 500);
}
});
})->create();
Теперь любое "неизвестное" исключение будет аккуратно перехвачено и превращено в JSON с кодом 500, не ломая ваше API и не показывая пользователю лишней информации.
6. Логирование ошибок: Черный ящик космического корабля
Настройки логирования в config/logging.php
:
<?php
'channels' => [
'space_api' => [
'driver' => 'daily',
'path' => storage_path('logs/space_api.log'),
'level' => 'error',
'days' => 14,
],
],
Добавление записи в лог:
<?php
try {
// Код с риском ошибки
} catch (Exception $e) {
Log::channel('space_api')->error('Ошибка доступа к планетам', [
'exception' => $e,
'request' => request()->all(),
'user_id' => auth()->id()
]);
throw $e;
}
Квиз для закрепления
🚀 Итог главы:
Вы оснастили свое API надежной системой спасения:
- 🛟 Глобальный перехват стандартных ошибок
- 🪐 Кастомные исключения с понятными кодами
- 📝 Единый JSON-формат для всех ошибок
- 🔍 Логирование с деталями инцидента
- 📡 Интеграция с системами мониторинга
Космический корабль готов к аварийным ситуациям! В финальной главе раздела мы протестируем все системы.
📌 Проверка:
- Создайте исключение
PlanetNotFoundException
- Добавьте обработку 404 ошибок в
->withExceptions
- Протестируйте запрос к несуществующей планете
⚠️ Если ошибки не перехватываются:
- Убедитесь что
is('api/*')
соответствует вашим роутам- Проверьте порядок обработчиков в
register()
- Для кастомных исключений используйте
throw new