Share
Embed
Transcript
49 slides
-
Anatomie des Request Handling - SymfonyLive Berlin 2023 Christian Flothmann
Software Developer bei QOSSMIC @xabbuh Symfony Core Team -
Anatomie des Request Handling - SymfonyLive Berlin 2023 symfony new
--demo symfony-live-demo symfony server:start -
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 -
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 -
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']); }; -
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']); }; -
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() ); -
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 -
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; } -
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; } -
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; } } -
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; } } -
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(); } } -
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(); } } -
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 -
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'))); } } } -
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'))); } } } -
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'))); } } } -
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 -
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 -
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 -
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 -
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 { } -
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; } -
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 -
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 -
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); } -
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')); } } } -
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); } -
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(); } } -
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 -
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)); } } } -
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(); } } -
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; } -
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; } -
Anatomie des Request Handling - SymfonyLive Berlin 2023 public function
terminate(Request $request, Response $response) { $this->dispatcher->dispatch(new TerminateEvent($this, $request, $response), KernelEvents::TERMINATE); } -
Anatomie des Request Handling - SymfonyLive Berlin 2023 TLDR HTTP
Kernel: Herz jeder Symfony Anwendung Event Dispatcher: mächtiger Erweiterungspunkt WARNUNG: dosierter Einsatz!