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

Anatomie des Request Handlings

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers. →

Anatomie des Request Handlings

SymfonyLive Berlin 2023

Christian Flothmann

October 06, 2023

Transcript

49 slides
  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!

More Decks by Christian Flothmann

Other Decks in Programming