vendor/symfony/http-kernel/HttpKernel.php line 122

  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\HttpKernel;
  11. use Symfony\Component\HttpFoundation\Exception\RequestExceptionInterface;
  12. use Symfony\Component\HttpFoundation\Request;
  13. use Symfony\Component\HttpFoundation\RequestStack;
  14. use Symfony\Component\HttpFoundation\Response;
  15. use Symfony\Component\HttpFoundation\StreamedResponse;
  16. use Symfony\Component\HttpKernel\Controller\ArgumentResolver;
  17. use Symfony\Component\HttpKernel\Controller\ArgumentResolverInterface;
  18. use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
  19. use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
  20. use Symfony\Component\HttpKernel\Event\ControllerEvent;
  21. use Symfony\Component\HttpKernel\Event\ExceptionEvent;
  22. use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
  23. use Symfony\Component\HttpKernel\Event\RequestEvent;
  24. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  25. use Symfony\Component\HttpKernel\Event\TerminateEvent;
  26. use Symfony\Component\HttpKernel\Event\ViewEvent;
  27. use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
  28. use Symfony\Component\HttpKernel\Exception\ControllerDoesNotReturnResponseException;
  29. use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
  30. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  31. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  32. // Help opcache.preload discover always-needed symbols
  33. class_exists(ControllerArgumentsEvent::class);
  34. class_exists(ControllerEvent::class);
  35. class_exists(ExceptionEvent::class);
  36. class_exists(FinishRequestEvent::class);
  37. class_exists(RequestEvent::class);
  38. class_exists(ResponseEvent::class);
  39. class_exists(TerminateEvent::class);
  40. class_exists(ViewEvent::class);
  41. class_exists(KernelEvents::class);
  42. /**
  43. * HttpKernel notifies events to convert a Request object to a Response one.
  44. *
  45. * @author Fabien Potencier <fabien@symfony.com>
  46. */
  47. class HttpKernel implements HttpKernelInterface, TerminableInterface
  48. {
  49. protected RequestStack $requestStack;
  50. private ArgumentResolverInterface $argumentResolver;
  51. private bool $terminating = false;
  52. public function __construct(
  53. protected EventDispatcherInterface $dispatcher,
  54. protected ControllerResolverInterface $resolver,
  55. ?RequestStack $requestStack = null,
  56. ?ArgumentResolverInterface $argumentResolver = null,
  57. private bool $handleAllThrowables = false,
  58. ) {
  59. $this->requestStack = $requestStack ?? new RequestStack();
  60. $this->argumentResolver = $argumentResolver ?? new ArgumentResolver();
  61. }
  62. public function handle(Request $request, int $type = HttpKernelInterface::MAIN_REQUEST, bool $catch = true): Response
  63. {
  64. $request->headers->set('X-Php-Ob-Level', (string) ob_get_level());
  65. $this->requestStack->push($request);
  66. $response = null;
  67. try {
  68. return $response = $this->handleRaw($request, $type);
  69. } catch (\Throwable $e) {
  70. if ($e instanceof \Error && !$this->handleAllThrowables) {
  71. throw $e;
  72. }
  73. if ($e instanceof RequestExceptionInterface) {
  74. $e = new BadRequestHttpException($e->getMessage(), $e);
  75. }
  76. if (false === $catch) {
  77. $this->finishRequest($request, $type);
  78. throw $e;
  79. }
  80. return $response = $this->handleThrowable($e, $request, $type);
  81. } finally {
  82. $this->requestStack->pop();
  83. if ($response instanceof StreamedResponse && $callback = $response->getCallback()) {
  84. $requestStack = $this->requestStack;
  85. $response->setCallback(static function () use ($request, $callback, $requestStack) {
  86. $requestStack->push($request);
  87. try {
  88. $callback();
  89. } finally {
  90. $requestStack->pop();
  91. }
  92. });
  93. }
  94. }
  95. }
  96. public function terminate(Request $request, Response $response): void
  97. {
  98. try {
  99. $this->terminating = true;
  100. $this->dispatcher->dispatch(new TerminateEvent($this, $request, $response), KernelEvents::TERMINATE);
  101. } finally {
  102. $this->terminating = false;
  103. }
  104. }
  105. /**
  106. * @internal
  107. */
  108. public function terminateWithException(\Throwable $exception, ?Request $request = null): void
  109. {
  110. if (!$request ??= $this->requestStack->getMainRequest()) {
  111. throw $exception;
  112. }
  113. if ($pop = $request !== $this->requestStack->getMainRequest()) {
  114. $this->requestStack->push($request);
  115. }
  116. try {
  117. $response = $this->handleThrowable($exception, $request, self::MAIN_REQUEST);
  118. } finally {
  119. if ($pop) {
  120. $this->requestStack->pop();
  121. }
  122. }
  123. $response->sendHeaders();
  124. $response->sendContent();
  125. $this->terminate($request, $response);
  126. }
  127. /**
  128. * Handles a request to convert it to a response.
  129. *
  130. * Exceptions are not caught.
  131. *
  132. * @throws \LogicException If one of the listener does not behave as expected
  133. * @throws NotFoundHttpException When controller cannot be found
  134. */
  135. private function handleRaw(Request $request, int $type = self::MAIN_REQUEST): Response
  136. {
  137. // request
  138. $event = new RequestEvent($this, $request, $type);
  139. $this->dispatcher->dispatch($event, KernelEvents::REQUEST);
  140. if ($event->hasResponse()) {
  141. return $this->filterResponse($event->getResponse(), $request, $type);
  142. }
  143. // load controller
  144. if (false === $controller = $this->resolver->getController($request)) {
  145. throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". The route is wrongly configured.', $request->getPathInfo()));
  146. }
  147. $event = new ControllerEvent($this, $controller, $request, $type);
  148. $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER);
  149. $controller = $event->getController();
  150. // controller arguments
  151. $arguments = $this->argumentResolver->getArguments($request, $controller, $event->getControllerReflector());
  152. $event = new ControllerArgumentsEvent($this, $event, $arguments, $request, $type);
  153. $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER_ARGUMENTS);
  154. $controller = $event->getController();
  155. $arguments = $event->getArguments();
  156. // call controller
  157. $response = $controller(...$arguments);
  158. // view
  159. if (!$response instanceof Response) {
  160. $event = new ViewEvent($this, $request, $type, $response, $event);
  161. $this->dispatcher->dispatch($event, KernelEvents::VIEW);
  162. if ($event->hasResponse()) {
  163. $response = $event->getResponse();
  164. } else {
  165. $msg = sprintf('The controller must return a "Symfony\Component\HttpFoundation\Response" object but it returned %s.', $this->varToString($response));
  166. // the user may have forgotten to return something
  167. if (null === $response) {
  168. $msg .= ' Did you forget to add a return statement somewhere in your controller?';
  169. }
  170. throw new ControllerDoesNotReturnResponseException($msg, $controller, __FILE__, __LINE__ - 17);
  171. }
  172. }
  173. return $this->filterResponse($response, $request, $type);
  174. }
  175. /**
  176. * Filters a response object.
  177. *
  178. * @throws \RuntimeException if the passed object is not a Response instance
  179. */
  180. private function filterResponse(Response $response, Request $request, int $type): Response
  181. {
  182. $event = new ResponseEvent($this, $request, $type, $response);
  183. $this->dispatcher->dispatch($event, KernelEvents::RESPONSE);
  184. $this->finishRequest($request, $type);
  185. return $event->getResponse();
  186. }
  187. /**
  188. * Publishes the finish request event, then pop the request from the stack.
  189. *
  190. * Note that the order of the operations is important here, otherwise
  191. * operations such as {@link RequestStack::getParentRequest()} can lead to
  192. * weird results.
  193. */
  194. private function finishRequest(Request $request, int $type): void
  195. {
  196. $this->dispatcher->dispatch(new FinishRequestEvent($this, $request, $type), KernelEvents::FINISH_REQUEST);
  197. }
  198. /**
  199. * Handles a throwable by trying to convert it to a Response.
  200. */
  201. private function handleThrowable(\Throwable $e, Request $request, int $type): Response
  202. {
  203. $event = new ExceptionEvent($this, $request, $type, $e, isKernelTerminating: $this->terminating);
  204. $this->dispatcher->dispatch($event, KernelEvents::EXCEPTION);
  205. // a listener might have replaced the exception
  206. $e = $event->getThrowable();
  207. if (!$event->hasResponse()) {
  208. $this->finishRequest($request, $type);
  209. throw $e;
  210. }
  211. $response = $event->getResponse();
  212. // the developer asked for a specific status code
  213. if (!$event->isAllowingCustomResponseCode() && !$response->isClientError() && !$response->isServerError() && !$response->isRedirect()) {
  214. // ensure that we actually have an error response
  215. if ($e instanceof HttpExceptionInterface) {
  216. // keep the HTTP status code and headers
  217. $response->setStatusCode($e->getStatusCode());
  218. $response->headers->add($e->getHeaders());
  219. } else {
  220. $response->setStatusCode(500);
  221. }
  222. }
  223. try {
  224. return $this->filterResponse($response, $request, $type);
  225. } catch (\Throwable $e) {
  226. if ($e instanceof \Error && !$this->handleAllThrowables) {
  227. throw $e;
  228. }
  229. return $response;
  230. }
  231. }
  232. /**
  233. * Returns a human-readable string for the specified variable.
  234. */
  235. private function varToString(mixed $var): string
  236. {
  237. if (\is_object($var)) {
  238. return sprintf('an object of type %s', $var::class);
  239. }
  240. if (\is_array($var)) {
  241. $a = [];
  242. foreach ($var as $k => $v) {
  243. $a[] = sprintf('%s => ...', $k);
  244. }
  245. return sprintf('an array ([%s])', mb_substr(implode(', ', $a), 0, 255));
  246. }
  247. if (\is_resource($var)) {
  248. return sprintf('a resource (%s)', get_resource_type($var));
  249. }
  250. if (null === $var) {
  251. return 'null';
  252. }
  253. if (false === $var) {
  254. return 'a boolean value (false)';
  255. }
  256. if (true === $var) {
  257. return 'a boolean value (true)';
  258. }
  259. if (\is_string($var)) {
  260. return sprintf('a string ("%s%s")', mb_substr($var, 0, 255), mb_strlen($var) > 255 ? '...' : '');
  261. }
  262. if (is_numeric($var)) {
  263. return sprintf('a number (%s)', (string) $var);
  264. }
  265. return (string) $var;
  266. }
  267. }