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

  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 $dispatcher;
  50. protected $resolver;
  51. protected $requestStack;
  52. private ArgumentResolverInterface $argumentResolver;
  53. private bool $handleAllThrowables;
  54. public function __construct(EventDispatcherInterface $dispatcher, ControllerResolverInterface $resolver, ?RequestStack $requestStack = null, ?ArgumentResolverInterface $argumentResolver = null, bool $handleAllThrowables = false)
  55. {
  56. $this->dispatcher = $dispatcher;
  57. $this->resolver = $resolver;
  58. $this->requestStack = $requestStack ?? new RequestStack();
  59. $this->argumentResolver = $argumentResolver ?? new ArgumentResolver();
  60. $this->handleAllThrowables = $handleAllThrowables;
  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. /**
  97. * @return void
  98. */
  99. public function terminate(Request $request, Response $response)
  100. {
  101. $this->dispatcher->dispatch(new TerminateEvent($this, $request, $response), KernelEvents::TERMINATE);
  102. }
  103. /**
  104. * @internal
  105. */
  106. public function terminateWithException(\Throwable $exception, ?Request $request = null): void
  107. {
  108. if (!$request ??= $this->requestStack->getMainRequest()) {
  109. throw $exception;
  110. }
  111. if ($pop = $request !== $this->requestStack->getMainRequest()) {
  112. $this->requestStack->push($request);
  113. }
  114. try {
  115. $response = $this->handleThrowable($exception, $request, self::MAIN_REQUEST);
  116. } finally {
  117. if ($pop) {
  118. $this->requestStack->pop();
  119. }
  120. }
  121. $response->sendHeaders();
  122. $response->sendContent();
  123. $this->terminate($request, $response);
  124. }
  125. /**
  126. * Handles a request to convert it to a response.
  127. *
  128. * Exceptions are not caught.
  129. *
  130. * @throws \LogicException If one of the listener does not behave as expected
  131. * @throws NotFoundHttpException When controller cannot be found
  132. */
  133. private function handleRaw(Request $request, int $type = self::MAIN_REQUEST): Response
  134. {
  135. // request
  136. $event = new RequestEvent($this, $request, $type);
  137. $this->dispatcher->dispatch($event, KernelEvents::REQUEST);
  138. if ($event->hasResponse()) {
  139. return $this->filterResponse($event->getResponse(), $request, $type);
  140. }
  141. // load controller
  142. if (false === $controller = $this->resolver->getController($request)) {
  143. throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". The route is wrongly configured.', $request->getPathInfo()));
  144. }
  145. $event = new ControllerEvent($this, $controller, $request, $type);
  146. $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER);
  147. $controller = $event->getController();
  148. // controller arguments
  149. $arguments = $this->argumentResolver->getArguments($request, $controller, $event->getControllerReflector());
  150. $event = new ControllerArgumentsEvent($this, $event, $arguments, $request, $type);
  151. $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER_ARGUMENTS);
  152. $controller = $event->getController();
  153. $arguments = $event->getArguments();
  154. // call controller
  155. $response = $controller(...$arguments);
  156. // view
  157. if (!$response instanceof Response) {
  158. $event = new ViewEvent($this, $request, $type, $response, $event);
  159. $this->dispatcher->dispatch($event, KernelEvents::VIEW);
  160. if ($event->hasResponse()) {
  161. $response = $event->getResponse();
  162. } else {
  163. $msg = sprintf('The controller must return a "Symfony\Component\HttpFoundation\Response" object but it returned %s.', $this->varToString($response));
  164. // the user may have forgotten to return something
  165. if (null === $response) {
  166. $msg .= ' Did you forget to add a return statement somewhere in your controller?';
  167. }
  168. throw new ControllerDoesNotReturnResponseException($msg, $controller, __FILE__, __LINE__ - 17);
  169. }
  170. }
  171. return $this->filterResponse($response, $request, $type);
  172. }
  173. /**
  174. * Filters a response object.
  175. *
  176. * @throws \RuntimeException if the passed object is not a Response instance
  177. */
  178. private function filterResponse(Response $response, Request $request, int $type): Response
  179. {
  180. $event = new ResponseEvent($this, $request, $type, $response);
  181. $this->dispatcher->dispatch($event, KernelEvents::RESPONSE);
  182. $this->finishRequest($request, $type);
  183. return $event->getResponse();
  184. }
  185. /**
  186. * Publishes the finish request event, then pop the request from the stack.
  187. *
  188. * Note that the order of the operations is important here, otherwise
  189. * operations such as {@link RequestStack::getParentRequest()} can lead to
  190. * weird results.
  191. */
  192. private function finishRequest(Request $request, int $type): void
  193. {
  194. $this->dispatcher->dispatch(new FinishRequestEvent($this, $request, $type), KernelEvents::FINISH_REQUEST);
  195. }
  196. /**
  197. * Handles a throwable by trying to convert it to a Response.
  198. */
  199. private function handleThrowable(\Throwable $e, Request $request, int $type): Response
  200. {
  201. $event = new ExceptionEvent($this, $request, $type, $e);
  202. $this->dispatcher->dispatch($event, KernelEvents::EXCEPTION);
  203. // a listener might have replaced the exception
  204. $e = $event->getThrowable();
  205. if (!$event->hasResponse()) {
  206. $this->finishRequest($request, $type);
  207. throw $e;
  208. }
  209. $response = $event->getResponse();
  210. // the developer asked for a specific status code
  211. if (!$event->isAllowingCustomResponseCode() && !$response->isClientError() && !$response->isServerError() && !$response->isRedirect()) {
  212. // ensure that we actually have an error response
  213. if ($e instanceof HttpExceptionInterface) {
  214. // keep the HTTP status code and headers
  215. $response->setStatusCode($e->getStatusCode());
  216. $response->headers->add($e->getHeaders());
  217. } else {
  218. $response->setStatusCode(500);
  219. }
  220. }
  221. try {
  222. return $this->filterResponse($response, $request, $type);
  223. } catch (\Throwable $e) {
  224. if ($e instanceof \Error && !$this->handleAllThrowables) {
  225. throw $e;
  226. }
  227. return $response;
  228. }
  229. }
  230. /**
  231. * Returns a human-readable string for the specified variable.
  232. */
  233. private function varToString(mixed $var): string
  234. {
  235. if (\is_object($var)) {
  236. return sprintf('an object of type %s', $var::class);
  237. }
  238. if (\is_array($var)) {
  239. $a = [];
  240. foreach ($var as $k => $v) {
  241. $a[] = sprintf('%s => ...', $k);
  242. }
  243. return sprintf('an array ([%s])', mb_substr(implode(', ', $a), 0, 255));
  244. }
  245. if (\is_resource($var)) {
  246. return sprintf('a resource (%s)', get_resource_type($var));
  247. }
  248. if (null === $var) {
  249. return 'null';
  250. }
  251. if (false === $var) {
  252. return 'a boolean value (false)';
  253. }
  254. if (true === $var) {
  255. return 'a boolean value (true)';
  256. }
  257. if (\is_string($var)) {
  258. return sprintf('a string ("%s%s")', mb_substr($var, 0, 255), mb_strlen($var) > 255 ? '...' : '');
  259. }
  260. if (is_numeric($var)) {
  261. return sprintf('a number (%s)', (string) $var);
  262. }
  263. return (string) $var;
  264. }
  265. }