Не исключено, что в процессе долгого путешествия от запроса до ответа, возникнет та или иная ошибка. По умолчанию, ядро проинструктировано перехватывать любое исключение и даже после этого оно пытается подобрать подходящий для него ответ Response
. Как мы уже видели, обработка каждого запроса обёрнута в блок try
/catch
:
public function handle(
Request $request,
$type = HttpKernelInterface::MASTER_REQUEST,
$catch = true
) {
try {
return $this->handleRaw($request, $type);
} catch (\Exception $e) {
if (false === $catch) {
throw $e;
}
return $this->handleException($e, $request, $type);
}
}
Когда переменная $catch
имеет значение истина
(true), вызывается метод handleException()
и ожидается, что он вернёт объект ответа. Этот метод отправляет событие KernelEvents::EXCEPTION
(kernel.exception) c экземпляром события GetResponseForExceptionEvent
.
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
private function handleException(\Exception $e, $request, $type)
{
$event = new GetResponseForExceptionEvent($this, $request, $type, $e);
$this->dispatcher->dispatch(KernelEvents::EXCEPTION, $event);
// a listener might have replaced the exception
$e = $event->getException();
if (!$event->hasResponse()) {
throw $e;
}
$response = $event->getResponse();
...
}
Слушатели события kernel.exception
могут:
- Установить объект ответа
Response
, соответствующий пойманному исключению. - Заменить исходный объект исключения.
Если ни один из слушателей не вызвал метод события setResponse()
, исключение будет вызвано еще раз, но в этот раз оно не будет перехвачено автоматически. Таким образом, если в настройках вашего интерпритатора PHP параметр display_errors
имеет значение истина
(true), PHP просто отобразит ошибку как есть, без изменений (если отображение ошибок у вас отключено — не будет выведено ничего). В случае же, если один из слушателей установил объект ответа Response
, класс HttpKernel
проверяет этот объект на предмет корректной установки http статус-кода:
// проверяем наличие специфического статус-кода
if ($response->headers->has('X-Status-Code')) {
$response->setStatusCode($response->headers->get('X-Status-Code'));
$response->headers->remove('X-Status-Code');
} elseif (
!$response->isClientError()
&& !$response->isServerError()
&& !$response->isRedirect()
) {
// убеждаемся, что у нас действительно есть ответ
if ($e instanceof HttpExceptionInterface) {
// сохраняем HTTP статус-код и заголовки
$response->setStatusCode($e->getStatusCode());
$response->headers->add($e->getHeaders());
} else {
$response->setStatusCode(500);
}
}
Это очень удобно, так как мы можем форсировать любой статус-код, добавляя заголовок X-Status-Code
к объекту ответа Response
(важно! это будет работать лишь для исключений, которые были перехвачены в HttpKernel
), или же создав исключение, которое реализует интерфейс HttpExceptionInterface
. В противном случае статус-код будет иметь значение по-умолчанию — 500, что означает Internal server error
. Это намного лучше, чем то, что предлагает нам «чистый» PHP, который в случае ошибки вернул бы ответ со статусом 200, что означает OK
. Когда слушатель установил объект ответа, этот ответ не будет обрабатываться каким-то иным образом, нежели обычный ответ, поэтому последний шаг в этом процессе — фильтрация ответа. Если в ходе фильтрации ответа будет сгенерировано другое исключение, это исключение будет проигнорировано и клиенту будет отправлен неотфильтрованный ответ:
try {
return $this->filterResponse($response, $request, $type);
} catch (\Exception $e) {
return $response;
}
Примечательные слушатели события kernel.exception
Слушатель ExceptionListener
компонента HttpKernel
пытается самостоятельно обработать исключение, логгируя его (если доступен логгер) и выполняя контроллер, который может отобразить страницу с информацией об ошибке. Как правило, это контроллер, который указан в файле конфигурации config.yml
:
twig:
# points to Symfony\Bundle\TwigBundle\Controller\ExceptionController
exception_controller: twig.controller.exception:showAction
Другой важный слушатель — это ExceptionListener
компонента Security
. Этот слушатель проверяет, является ли исходное исключение экземпляром AuthenticationException
или же AccessDeniedException
. В первом случае он начинает процесс аутентификации, если это возможно. Во втором случае он пытается перелогинить пользователя (например, если тот использовал remember me
опцию при логине), если же это не выходит, предоставляет возможность access denied handler
разобраться с возникшей ситуацией.