Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Anatomie des Request Handlings

Anatomie des Request Handlings

SymfonyLive Berlin 2023

Christian Flothmann

October 06, 2023
Tweet

More Decks by Christian Flothmann

Other Decks in Programming

Transcript

  1. Anatomie des Request Handling - SymfonyLive Berlin 2023 Christian Flothmann

    Software Developer bei QOSSMIC @xabbuh Symfony Core Team
  2. Anatomie des Request Handling - SymfonyLive Berlin 2023 symfony new

    --demo symfony-live-demo symfony server:start
  3. Anatomie des Request Handling - SymfonyLive Berlin 2023 A controller

    is a PHP function you create that reads information from the Request object and creates and returns a Response object. https://symfony.com/doc/current/controller.html
  4. Anatomie des Request Handling - SymfonyLive Berlin 2023 The HttpKernel

    component provides a structured process for converting a Request into a Response by making use of the EventDispatcher component. The HttpKernel component provides a structured process for converting a Request into a Response by making use of the EventDispatcher component. The HttpKernel component provides a structured process for converting a Request into a Response by making use of the EventDispatcher component. https://symfony.com/doc/current/components/http_kernel.html
  5. Anatomie des Request Handling - SymfonyLive Berlin 2023 use App\Kernel;

    require_once dirname(__DIR__).'/vendor/autoload_runtime.php'; return function (array $context) { return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']); };
  6. Anatomie des Request Handling - SymfonyLive Berlin 2023 $runtime =

    $_SERVER['APP_RUNTIME'] ?? $_ENV['APP_RUNTIME'] ?? 'Symfony\\Component\\Runtime\\Symfo $runtime = new $runtime(($_SERVER['APP_RUNTIME_OPTIONS'] ?? $_ENV['APP_RUNTIME_OPTIONS'] ?? []) + 'project_dir' => dirname(__DIR__, 1), ]); [$app, $args] = $runtime ->getResolver($app) ->resolve(); $app = $app(...$args); exit( $runtime ->getRunner($app) ->run() ); return function (array $context) { return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']); };
  7. Anatomie des Request Handling - SymfonyLive Berlin 2023 $runtime =

    $_SERVER['APP_RUNTIME'] ?? $_ENV['APP_RUNTIME'] ?? 'Symfony\\Component\\Runtime\\Symfo $runtime = new $runtime(($_SERVER['APP_RUNTIME_OPTIONS'] ?? $_ENV['APP_RUNTIME_OPTIONS'] ?? []) + 'project_dir' => dirname(__DIR__, 1), ]); [$app, $args] = $runtime ->getResolver($app) ->resolve(); $app = $app(...$args); exit( $runtime ->getRunner($app) ->run() );
  8. Anatomie des Request Handling - SymfonyLive Berlin 2023 public function

    getRunner(?object $application): RunnerInterface { if ($application instanceof HttpKernelInterface) { return new HttpKernelRunner($application, Request::createFromGlobals()); } if ($application instanceof Response) { return new ResponseRunner($application); } if ($application instanceof Command) { $console = $this->console ??= new Application(); $console->setName($application->getName() ?: $console->getName()); if (!$application->getName() || !$console->has($application->getName())) { $application->setName($_SERVER['argv'][0]); $console->add($application); } Anatomie des Request Handling - SymfonyLive Berlin 2023
  9. Anatomie des Request Handling - SymfonyLive Berlin 2023 public static

    function createFromGlobals(): static { $request = self::createRequestFromFactory($_GET, $_POST, [], $_COOKIE, $_FILES, $_SERVER); if (str_starts_with($request->headers->get('CONTENT_TYPE', ''), 'application/x-www-form-urlencoded') && \in_array(strtoupper($request->server->get('REQUEST_METHOD', 'GET')), ['PUT', 'DELETE', 'PATCH']) ) { parse_str($request->getContent(), $data); $request->request = new InputBag($data); } return $request; }
  10. Anatomie des Request Handling - SymfonyLive Berlin 2023 public function

    run(): int { $response = $this->kernel->handle($this->request); $response->send(); if ($this->kernel instanceof TerminableInterface) { $this->kernel->terminate($this->request, $response); } return 0; }
  11. Anatomie des Request Handling - SymfonyLive Berlin 2023 public function

    handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response { if (!$this->booted) { $container = $this->container ?? $this->preBoot(); if ($container->has('http_cache')) { return $container->get('http_cache')->handle($request, $type, $catch); } } $this->boot(); ++$this->requestStackSize; $this->resetServices = true; try { return $this->getHttpKernel()->handle($request, $type, $catch); } finally { --$this->requestStackSize; } }
  12. Anatomie des Request Handling - SymfonyLive Berlin 2023 public function

    handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response { if (!$this->booted) { $container = $this->container ?? $this->preBoot(); if ($container->has('http_cache')) { return $container->get('http_cache')->handle($request, $type, $catch); } } $this->boot(); ++$this->requestStackSize; $this->resetServices = true; try { return $this->getHttpKernel()->handle($request, $type, $catch); } finally { --$this->requestStackSize; } }
  13. Anatomie des Request Handling - SymfonyLive Berlin 2023 public function

    handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response { $request->headers->set('X-Php-Ob-Level', (string) ob_get_level()); $this->requestStack->push($request); try { return $this->handleRaw($request, $type); } catch (\Throwable $e) { if ($e instanceof \Error && !$this->handleAllThrowables) { throw $e; } if ($e instanceof RequestExceptionInterface) { $e = new BadRequestHttpException($e->getMessage(), $e); } if (false === $catch) { $this->finishRequest($request, $type); throw $e; } return $this->handleThrowable($e, $request, $type); } finally { $this->requestStack->pop(); } }
  14. Anatomie des Request Handling - SymfonyLive Berlin 2023 public function

    handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response { $request->headers->set('X-Php-Ob-Level', (string) ob_get_level()); $this->requestStack->push($request); try { return $this->handleRaw($request, $type); } catch (\Throwable $e) { if ($e instanceof \Error && !$this->handleAllThrowables) { throw $e; } if ($e instanceof RequestExceptionInterface) { $e = new BadRequestHttpException($e->getMessage(), $e); } if (false === $catch) { $this->finishRequest($request, $type); throw $e; } return $this->handleThrowable($e, $request, $type); } finally { $this->requestStack->pop(); } }
  15. Anatomie des Request Handling - SymfonyLive Berlin 2023 private function

    handleRaw(Request $request, int $type = self::MAIN_REQUEST): Response { // request $event = new RequestEvent($this, $request, $type); $this->dispatcher->dispatch($event, KernelEvents::REQUEST); if ($event->hasResponse()) { return $this->filterResponse($event->getResponse(), $request, $type); } // load controller if (false === $controller = $this->resolver->getController($request)) { throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". The route is wrongly configured.', $request->getPathInfo())); } $event = new ControllerEvent($this, $controller, $request, $type); $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER); $controller = $event->getController(); // controller arguments $arguments = $this->argumentResolver->getArguments($request, $controller, $event- >getControllerReflector()); $event = new ControllerArgumentsEvent($this, $event, $arguments, $request, $type); $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER_ARGUMENTS); $controller = $event->getController(); Anatomie des Request Handling - SymfonyLive Berlin 2023
  16. Anatomie des Request Handling - SymfonyLive Berlin 2023 #[AsEventListener(event: RequestEvent::class)]

    class MaintenanceModeListener { public function __construct( private readonly bool $enabled, private readonly Environment $twig, ) { } public function __invoke(RequestEvent $event) { if ($this->enabled) { $event->setResponse(new Response($this->twig->render('maintenance.html.twig'))); } } }
  17. Anatomie des Request Handling - SymfonyLive Berlin 2023 #[AsEventListener(event: RequestEvent::class)]

    class MaintenanceModeListener { public function __construct( private readonly bool $enabled, private readonly Environment $twig, ) { } public function __invoke(RequestEvent $event) { if ($this->enabled) { $event->setResponse(new Response($this->twig->render('maintenance.html.twig'))); } } }
  18. Anatomie des Request Handling - SymfonyLive Berlin 2023 #[AsEventListener(event: RequestEvent::class)]

    class MaintenanceModeListener { public function __construct( private readonly bool $enabled, private readonly Environment $twig, ) { } public function __invoke(RequestEvent $event) { if ($this->enabled) { $event->setResponse(new Response($this->twig->render('maintenance.html.twig'))); } } }
  19. Anatomie des Request Handling - SymfonyLive Berlin 2023 private function

    handleRaw(Request $request, int $type = self::MAIN_REQUEST): Response { // request $event = new RequestEvent($this, $request, $type); $this->dispatcher->dispatch($event, KernelEvents::REQUEST); if ($event->hasResponse()) { return $this->filterResponse($event->getResponse(), $request, $type); } // load controller if (false === $controller = $this->resolver->getController($request)) { throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". The route is wrongly configured.', $request->getPathInfo())); } $event = new ControllerEvent($this, $controller, $request, $type); $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER); $controller = $event->getController(); // controller arguments $arguments = $this->argumentResolver->getArguments($request, $controller, $event- >getControllerReflector()); $event = new ControllerArgumentsEvent($this, $event, $arguments, $request, $type); $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER_ARGUMENTS); $controller = $event->getController(); Anatomie des Request Handling - SymfonyLive Berlin 2023
  20. Anatomie des Request Handling - SymfonyLive Berlin 2023 private function

    handleRaw(Request $request, int $type = self::MAIN_REQUEST): Response { // request $event = new RequestEvent($this, $request, $type); $this->dispatcher->dispatch($event, KernelEvents::REQUEST); if ($event->hasResponse()) { return $this->filterResponse($event->getResponse(), $request, $type); } // load controller if (false === $controller = $this->resolver->getController($request)) { throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". The route is wrongly configured.', $request->getPathInfo())); } $event = new ControllerEvent($this, $controller, $request, $type); $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER); $controller = $event->getController(); // controller arguments $arguments = $this->argumentResolver->getArguments($request, $controller, $event- >getControllerReflector()); $event = new ControllerArgumentsEvent($this, $event, $arguments, $request, $type); $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER_ARGUMENTS); $controller = $event->getController(); Anatomie des Request Handling - SymfonyLive Berlin 2023
  21. Anatomie des Request Handling - SymfonyLive Berlin 2023 private function

    handleRaw(Request $request, int $type = self::MAIN_REQUEST): Response { // request $event = new RequestEvent($this, $request, $type); $this->dispatcher->dispatch($event, KernelEvents::REQUEST); if ($event->hasResponse()) { return $this->filterResponse($event->getResponse(), $request, $type); } // load controller if (false === $controller = $this->resolver->getController($request)) { throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". The route is wrongly configured.', $request->getPathInfo())); } $event = new ControllerEvent($this, $controller, $request, $type); $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER); $controller = $event->getController(); // controller arguments $arguments = $this->argumentResolver->getArguments($request, $controller, $event- >getControllerReflector()); $event = new ControllerArgumentsEvent($this, $event, $arguments, $request, $type); $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER_ARGUMENTS); $controller = $event->getController(); Anatomie des Request Handling - SymfonyLive Berlin 2023
  22. Anatomie des Request Handling - SymfonyLive Berlin 2023 } $event

    = new ControllerEvent($this, $controller, $request, $type); $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER); $controller = $event->getController(); // controller arguments $arguments = $this->argumentResolver->getArguments($request, $controller, $event- >getControllerReflector()); $event = new ControllerArgumentsEvent($this, $event, $arguments, $request, $type); $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER_ARGUMENTS); $controller = $event->getController(); $arguments = $event->getArguments(); // call controller $response = $controller(...$arguments); // view if (!$response instanceof Response) { $event = new ViewEvent($this, $request, $type, $response, $event); $this->dispatcher->dispatch($event, KernelEvents::VIEW); if ($event->hasResponse()) { $response = $event->getResponse(); } else { $msg = sprintf('The controller must return a "Symfony\Component\HttpFoundation\Response" object but it returned %s.', $this- Anatomie des Request Handling - SymfonyLive Berlin 2023
  23. Anatomie des Request Handling - SymfonyLive Berlin 2023 #[Route('/', name:

    'blog_index', defaults: ['page' => '1', '_format' => 'html'], methods: ['GET'])] #[Route('/page/{page<[1-9]\d{0,8}>}', name: 'blog_index_paginated', methods: ['GET'])] public function index(Request $request, int $page, PostRepository $posts): Response { } #[Route('/comment/{postSlug}/new', name: 'comment_new', methods: ['POST'])] public function commentNew( #[CurrentUser] User $user, Request $request, #[MapEntity(mapping: ['postSlug' => 'slug'])] Post $post, EventDispatcherInterface $eventDispatcher, EntityManagerInterface $entityManager, ): Response { }
  24. Anatomie des Request Handling - SymfonyLive Berlin 2023 namespace Symfony\Component\HttpKernel\Controller;

    use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata; interface ValueResolverInterface { public function resolve(Request $request, ArgumentMetadata $argument): iterable; }
  25. Anatomie des Request Handling - SymfonyLive Berlin 2023 $controller =

    $event->getController(); $arguments = $event->getArguments(); // call controller $response = $controller(...$arguments); // view if (!$response instanceof Response) { $event = new ViewEvent($this, $request, $type, $response, $event); $this->dispatcher->dispatch($event, KernelEvents::VIEW); if ($event->hasResponse()) { $response = $event->getResponse(); } else { $msg = sprintf('The controller must return a "Symfony\Component\HttpFoundation\Response" object but it returned %s.', $this- >varToString($response)); // the user may have forgotten to return something if (null === $response) { $msg .= ' Did you forget to add a return statement somewhere in your controller?'; } throw new ControllerDoesNotReturnResponseException($msg, $controller, __FILE__, __LINE__ - 17); } } Anatomie des Request Handling - SymfonyLive Berlin 2023
  26. Anatomie des Request Handling - SymfonyLive Berlin 2023 $controller =

    $event->getController(); $arguments = $event->getArguments(); // call controller $response = $controller(...$arguments); // view if (!$response instanceof Response) { $event = new ViewEvent($this, $request, $type, $response, $event); $this->dispatcher->dispatch($event, KernelEvents::VIEW); if ($event->hasResponse()) { $response = $event->getResponse(); } else { $msg = sprintf('The controller must return a "Symfony\Component\HttpFoundation\Response" object but it returned %s.', $this- >varToString($response)); // the user may have forgotten to return something if (null === $response) { $msg .= ' Did you forget to add a return statement somewhere in your controller?'; } throw new ControllerDoesNotReturnResponseException($msg, $controller, __FILE__, __LINE__ - 17); } } Anatomie des Request Handling - SymfonyLive Berlin 2023
  27. Anatomie des Request Handling - SymfonyLive Berlin 2023 private function

    filterResponse(Response $response, Request $request, int $type): Response { $event = new ResponseEvent($this, $request, $type, $response); $this->dispatcher->dispatch($event, KernelEvents::RESPONSE); $this->finishRequest($request, $type); return $event->getResponse(); } private function finishRequest(Request $request, int $type): void { $this->dispatcher->dispatch(new FinishRequestEvent($this, $request, $type), KernelEvents::FINISH_REQUEST); }
  28. Anatomie des Request Handling - SymfonyLive Berlin 2023 use Symfony\Component\EventDispatcher\Attribute\AsEventListener;

    use Symfony\Component\HttpKernel\Event\ResponseEvent; #[AsEventListener(event: ResponseEvent::class)] class RequestIdResponseListener { public function __invoke(ResponseEvent $event): void { $request = $event->getRequest(); $response = $event->getResponse(); if ($request->attributes->has('_request_id')) { $response->headers->set('X-Request-Id', $request->attributes->get('_request_id')); } } }
  29. Anatomie des Request Handling - SymfonyLive Berlin 2023 private function

    filterResponse(Response $response, Request $request, int $type): Response { $event = new ResponseEvent($this, $request, $type, $response); $this->dispatcher->dispatch($event, KernelEvents::RESPONSE); $this->finishRequest($request, $type); return $event->getResponse(); } private function finishRequest(Request $request, int $type): void { $this->dispatcher->dispatch(new FinishRequestEvent($this, $request, $type), KernelEvents::FINISH_REQUEST); }
  30. Anatomie des Request Handling - SymfonyLive Berlin 2023 public function

    handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response { $request->headers->set('X-Php-Ob-Level', (string) ob_get_level()); $this->requestStack->push($request); try { return $this->handleRaw($request, $type); } catch (\Throwable $e) { if ($e instanceof \Error && !$this->handleAllThrowables) { throw $e; } if ($e instanceof RequestExceptionInterface) { $e = new BadRequestHttpException($e->getMessage(), $e); } if (false === $catch) { $this->finishRequest($request, $type); throw $e; } return $this->handleThrowable($e, $request, $type); } finally { $this->requestStack->pop(); } }
  31. Anatomie des Request Handling - SymfonyLive Berlin 2023 private function

    handleThrowable(\Throwable $e, Request $request, int $type): Response { $event = new ExceptionEvent($this, $request, $type, $e); $this->dispatcher->dispatch($event, KernelEvents::EXCEPTION); // a listener might have replaced the exception $e = $event->getThrowable(); if (!$event->hasResponse()) { $this->finishRequest($request, $type); throw $e; } $response = $event->getResponse(); // the developer asked for a specific status code if (!$event->isAllowingCustomResponseCode() && !$response->isClientError() && !$response- >isServerError() && !$response->isRedirect()) { // ensure that we actually have an error response if ($e instanceof HttpExceptionInterface) { Anatomie des Request Handling - SymfonyLive Berlin 2023
  32. Anatomie des Request Handling - SymfonyLive Berlin 2023 #[AsEventListener(event: ExceptionEvent::class)]

    class UnprocessableEntityListener { public function __invoke(ExceptionEvent $event): void { if ($event->getThrowable() instanceof DiscountExpiredException) { $event->setResponse(new Response('', Response::HTTP_UNPROCESSABLE_ENTITY)); } } }
  33. Anatomie des Request Handling - SymfonyLive Berlin 2023 public function

    handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response { $request->headers->set('X-Php-Ob-Level', (string) ob_get_level()); $this->requestStack->push($request); try { return $this->handleRaw($request, $type); } catch (\Throwable $e) { if ($e instanceof \Error && !$this->handleAllThrowables) { throw $e; } if ($e instanceof RequestExceptionInterface) { $e = new BadRequestHttpException($e->getMessage(), $e); } if (false === $catch) { $this->finishRequest($request, $type); throw $e; } return $this->handleThrowable($e, $request, $type); } finally { $this->requestStack->pop(); } }
  34. Anatomie des Request Handling - SymfonyLive Berlin 2023 public function

    run(): int { $response = $this->kernel->handle($this->request); $response->send(); if ($this->kernel instanceof TerminableInterface) { $this->kernel->terminate($this->request, $response); } return 0; }
  35. Anatomie des Request Handling - SymfonyLive Berlin 2023 public function

    run(): int { $response = $this->kernel->handle($this->request); $response->send(); if ($this->kernel instanceof TerminableInterface) { $this->kernel->terminate($this->request, $response); } return 0; }
  36. Anatomie des Request Handling - SymfonyLive Berlin 2023 public function

    terminate(Request $request, Response $response) { $this->dispatcher->dispatch(new TerminateEvent($this, $request, $response), KernelEvents::TERMINATE); }
  37. Anatomie des Request Handling - SymfonyLive Berlin 2023 TLDR HTTP

    Kernel: Herz jeder Symfony Anwendung Event Dispatcher: mächtiger Erweiterungspunkt WARNUNG: dosierter Einsatz!