По мотивам вопроса в чате...
Внимание! Это не обучающая статья, раскрывающая самые базовые принципы работы с исключениями. Предполагается, что читатель знаком с исключениями и их обработкой в общем, а так же внимательно ознакомился с разделом Errors & Logging официальной документации.
Вся работа с исключения происходит в файле app/Exceptions/Handler.php, в котором есть два метода —
report()
, отвечающий за логирование исключения и render()
, отвечающий за формирование представления,
а так же массив $dontReport
, содержащий имена классов исключений, которые логировать не надо.
Начнём стого, что дополним $dontReport
:
protected $dontReport = [
\Illuminate\Session\TokenMismatchException::class,
\Symfony\Component\HttpKernel\Exception\HttpException::class
];
Т.е. мы не хотим логировать всякие ошибки типа 404 и невалидные CSRF-токены.
Теперь изменим код метода render()
:
public function render($request, \Exception $e)
{
$statusCode = $this->getStatusCode($e);
if ($request->wantsJson()) {
return response()->json(['message' => $this->getMessage($e)], $statusCode);
}
// в режиме отладки выводим все ошибки как есть
if (config('app.debug')) {
return parent::render($request, $e);
}
// если это потомок \Symfony\Component\HttpKernel\Exception\HttpException
if ($this->isHttpException($e)) {
return $this->renderHttpException($e);
}
// иначе показываем стандартную страницу ошибки
return response()->view('errors.500', [], 500);
}
Почему wantsJson()
, а не ajax()
? Потому что ajax это не обязательно ответ в json'е, плюс некоторые
клиентские библиотеки не устанавливают заголовок X-Requested-With: XMLHttpRequest
.
В любом случае, вы всегда можете изменить условие проверки.
Добавим недостающие методы:
protected function getStatusCode(\Exception $e)
{
if ($e instanceof HttpException) {
return $e->getStatusCode();
}
// данное исключение не является потомком \Symfony\Component\HttpKernel\Exception\HttpException,
// поэтому небольшой хак
if ($e instanceof ModelNotFoundException) {
return 404;
}
return 500;
}
protected function getMessage(\Exception $e)
{
// это исключение я создал сам и использую в моделях,
// у него человекопонятные сообщения типа «Не удалось сохранить запись»
if ($e instanceof DatabaseException) {
return $e->getMessage();
}
if ($e instanceof ModelNotFoundException) {
return trans('main.model_not_found');
}
return trans('main.something_wrong');
}
Метод getMessage()
нужен затем, что не следует показывать пользователю
«сырое» сообщение из исключения — мало ли какая секретная информация там окажется.
Теперь рассмотрим одну из самых частых ситуаций, когда при очередном запросе сервер возвращает исключение TokenMismatchException.
TokenMismatchException говорит о том, что присланный клиентом токен не совпадает с имеющимся в сессии токеном. Причина этому одна — старая сессия «протухла» и при очередном запросе началась новая.
Если пользователь не аутентифицирован, то проблем нет — роуты, доступные широкому кругу анонимных лиц, надо вносить в исключения посредника VerifyCsrfToken, что бы проверка токена к ним не применялась.
Но что делать, если пользователь начал заполнять какую-то форму в админке, потом на два дня ушел за хлебом, вернулся и нажал кнопку «Сохранить»?
В посреднике VerifyCsrfToken модифицируем метод handle
:
public function handle($request, Closure $next)
{
try {
return parent::handle($request, $next);
} catch (TokenMismatchException $e) {
if ($request->wantsJson()) {
return responce()->(['message' => 'Надо залогиниться'], 418);
}
return redirect()->route('auth.login.show');
}
}
Т.е. если это был ajax-запрос, мы вернем нормальный json-ответ. Код 418 выбран для удобства, что ты было проще отслеживать это событие на клиенте. При получении такого ответа клиент может сохранить данные формы в localStorage, отправить пользователя на страницу логина и т.д.
Можно дополнительно выбрасывать ещё одно исключение, чтобы логгировать факт «протухания» сессии/токена.
Данный вариант не является универсальным, при большом количестве исключений, требующих особой обработки или условий, меняющих поведение обработчика, метод render()
может превратиться в «расчёску» из if...else...endif
и иных конструкций.
change responce to response