SymfonySeniorTechnical

Что такое Symfony HttpKernel и как он обрабатывает запрос?

HttpKernel обрабатывает запрос через цепочку событий: kernel.request (роутинг/auth), kernel.controller, kernel.controller_arguments, вызов контроллера, kernel.view, kernel.response и kernel.terminate после отправки ответа.

Symfony HttpKernel: жизненный цикл запроса

HttpKernel — это сердце Symfony. Он принимает объект Request и возвращает Response, используя цепочку событий (Event Dispatcher). Весь Symfony-фреймворк — это лишь набор EventListener'ов, зарегистрированных на события ядра.

Схема обработки запроса

Request
  └── kernel.request        # аутентификация, locale, роутинг
        └── kernel.controller  # resolve controller callable
              └── kernel.controller_arguments  # ParamConverter, ValueResolver
                    └── Controller::action()   # бизнес-логика
                          └── kernel.view      # если вернули не Response
                                └── kernel.response  # модификация заголовков, Set-Cookie
                                      └── kernel.finish_request
                                            └── kernel.terminate  # после flush (logging, async)

Ключевые события

  • kernel.request — приходит самым первым. RouterListener слушает это событие и добавляет атрибуты роутинга (_controller, _route) в Request. FirewallListener проверяет аутентификацию.
  • kernel.controller — ControllerResolver уже определил контроллер. Можно подменить его через $event->setController().
  • kernel.controller_arguments — ArgumentValueResolver'ы разрешают аргументы метода: #[MapRequestPayload], #[CurrentUser], Entity по ID и т.д.
  • kernel.view — если контроллер вернул не Response, а массив или DTO, ViewHandler (например, из FOSRestBundle) конвертирует его в Response.
  • kernel.response — финальная обработка ответа: добавление Cache-Control, CORS-заголовков, установка cookie.
  • kernel.terminate — выполняется после отправки ответа клиенту (fastcgi_finish_request). Используется для отложенного логирования и очередей.
  • kernel.exception — перехватывает любое непойманное исключение и конвертирует в Response через ExceptionListener.

Исходный код HttpKernel::handle()

<?php
// Упрощённый вариант Symfony\Component\HttpKernel\HttpKernel::handle()
public function handle(Request $request, int $type = self::MAIN_REQUEST, bool $catch = true): Response
{
    try {
        return $this->handleRaw($request, $type);
    } catch (\Throwable $e) {
        if (!$catch) {
            throw $e;
        }
        return $this->handleThrowable($e, $request, $type);
    }
}

private function handleRaw(Request $request, int $requestType): Response
{
    // 1. kernel.request
    $event = new RequestEvent($this, $request, $requestType);
    $this->dispatcher->dispatch($event, KernelEvents::REQUEST);
    if ($event->hasResponse()) {
        return $this->filterResponse($event->getResponse(), $request, $requestType);
    }

    // 2. Resolve controller
    $controller = $this->resolver->getController($request);

    // 3. kernel.controller
    $event = new ControllerEvent($this, $controller, $request, $requestType);
    $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER);
    $controller = $event->getController();

    // 4. kernel.controller_arguments
    $arguments = $this->argumentResolver->getArguments($request, $controller);
    $event = new ControllerArgumentsEvent($this, $controller, $arguments, $request, $requestType);
    $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER_ARGUMENTS);

    // 5. Call controller
    $response = $controller(...$event->getArguments());

    // 6. kernel.view (если не Response)
    if (!$response instanceof Response) {
        $event = new ViewEvent($this, $request, $requestType, $response);
        $this->dispatcher->dispatch($event, KernelEvents::VIEW);
        $response = $event->getResponse();
    }

    // 7. kernel.response
    return $this->filterResponse($response, $request, $requestType);
}

Sub-requests

HttpKernel поддерживает вложенные запросы (sub-request) — это основа для ESI и функции {{ render(controller('App\Controller\WidgetController::index')) }} в Twig. Каждый sub-request проходит весь цикл событий заново.

Создание собственного EventListener

<?php
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;

#[AsEventListener(event: KernelEvents::REQUEST, priority: 20)]
class LocaleListener
{
    public function __invoke(RequestEvent $event): void
    {
        if (!$event->isMainRequest()) {
            return; // Пропустить sub-requests
        }
        $request = $event->getRequest();
        $locale = $request->cookies->get('locale', 'en');
        $request->setLocale($locale);
    }
}

Подводные камни

  • kernel.terminate работает только при использовании FastCGI или специального терминируемого runner'а — в обычном PHP-FPM он выполняется синхронно и задерживает ответ.
  • Приоритеты листенеров: RouterListener имеет приоритет 32, FirewallListener — 8; если ваш листенер обращается к _route-атрибутам, его приоритет должен быть ниже 32.
  • Проверка $event->isMainRequest() обязательна в листенерах, которые не должны срабатывать для sub-requests (ESI, forward).
  • ExceptionListener вызывает handle() рекурсивно с $catch = false — бесконечная рекурсия возможна, если обработчик исключений сам бросает исключение.
  • kernel.view диспатчится только если контроллер вернул не Response; если вернул null — будет TypeError, а не ViewEvent.
  • #[AsEventListener] требует Symfony 6.0+; в более старых версиях нужна ручная регистрация через services.yaml с тегом kernel.event_listener.

Common mistakes

  • Сводить httpkernel request lifecycle к названию метода без lifecycle и failure path.
  • Игнорировать модель runtime: Symfony 7/8 строит request lifecycle вокруг HttpKernel, events, routing, controller resolver и response listeners.
  • Не отделять validation, authorization, transaction boundary и business logic.

What the interviewer is testing

  • Объясняет httpkernel request lifecycle через конкретную точку lifecycle в Symfony.
  • Приводит корректный минимальный пример без вымышленных методов или callbacks.
  • Называет edge cases: пустые значения, ошибки, транзакции, безопасность или concurrency.

Sources

Related topics