annotate core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 1fec387a4317
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\Core\EventSubscriber;
Chris@0 4
Chris@14 5 use Drupal\Core\Cache\CacheableResponseInterface;
Chris@0 6 use Drupal\Core\Routing\RedirectDestinationInterface;
Chris@0 7 use Drupal\Core\Utility\Error;
Chris@0 8 use Psr\Log\LoggerInterface;
Chris@0 9 use Symfony\Component\HttpFoundation\Response;
Chris@0 10 use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
Chris@0 11 use Symfony\Component\HttpKernel\HttpKernelInterface;
Chris@0 12 use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
Chris@0 13 use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
Chris@0 14
Chris@0 15 /**
Chris@0 16 * Exception subscriber for handling core default HTML error pages.
Chris@0 17 */
Chris@0 18 class DefaultExceptionHtmlSubscriber extends HttpExceptionSubscriberBase {
Chris@0 19
Chris@0 20 /**
Chris@0 21 * The HTTP kernel.
Chris@0 22 *
Chris@0 23 * @var \Symfony\Component\HttpKernel\HttpKernelInterface
Chris@0 24 */
Chris@0 25 protected $httpKernel;
Chris@0 26
Chris@0 27 /**
Chris@0 28 * The logger instance.
Chris@0 29 *
Chris@0 30 * @var \Psr\Log\LoggerInterface
Chris@0 31 */
Chris@0 32 protected $logger;
Chris@0 33
Chris@0 34 /**
Chris@0 35 * The redirect destination service.
Chris@0 36 *
Chris@0 37 * @var \Drupal\Core\Routing\RedirectDestinationInterface
Chris@0 38 */
Chris@0 39 protected $redirectDestination;
Chris@0 40
Chris@0 41 /**
Chris@0 42 * A router implementation which does not check access.
Chris@0 43 *
Chris@0 44 * @var \Symfony\Component\Routing\Matcher\UrlMatcherInterface
Chris@0 45 */
Chris@0 46 protected $accessUnawareRouter;
Chris@0 47
Chris@0 48 /**
Chris@0 49 * Constructs a new DefaultExceptionHtmlSubscriber.
Chris@0 50 *
Chris@0 51 * @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
Chris@0 52 * The HTTP kernel.
Chris@0 53 * @param \Psr\Log\LoggerInterface $logger
Chris@0 54 * The logger service.
Chris@0 55 * @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination
Chris@0 56 * The redirect destination service.
Chris@0 57 * @param \Symfony\Component\Routing\Matcher\UrlMatcherInterface $access_unaware_router
Chris@0 58 * A router implementation which does not check access.
Chris@0 59 */
Chris@0 60 public function __construct(HttpKernelInterface $http_kernel, LoggerInterface $logger, RedirectDestinationInterface $redirect_destination, UrlMatcherInterface $access_unaware_router) {
Chris@0 61 $this->httpKernel = $http_kernel;
Chris@0 62 $this->logger = $logger;
Chris@0 63 $this->redirectDestination = $redirect_destination;
Chris@0 64 $this->accessUnawareRouter = $access_unaware_router;
Chris@0 65 }
Chris@0 66
Chris@0 67 /**
Chris@0 68 * {@inheritdoc}
Chris@0 69 */
Chris@0 70 protected static function getPriority() {
Chris@0 71 // A very low priority so that custom handlers are almost certain to fire
Chris@0 72 // before it, even if someone forgets to set a priority.
Chris@0 73 return -128;
Chris@0 74 }
Chris@0 75
Chris@0 76 /**
Chris@0 77 * {@inheritdoc}
Chris@0 78 */
Chris@0 79 protected function getHandledFormats() {
Chris@0 80 return ['html'];
Chris@0 81 }
Chris@0 82
Chris@0 83 /**
Chris@0 84 * Handles a 4xx error for HTML.
Chris@0 85 *
Chris@0 86 * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
Chris@0 87 * The event to process.
Chris@0 88 */
Chris@0 89 public function on4xx(GetResponseForExceptionEvent $event) {
Chris@0 90 if (($exception = $event->getException()) && $exception instanceof HttpExceptionInterface) {
Chris@0 91 $this->makeSubrequest($event, '/system/4xx', $exception->getStatusCode());
Chris@0 92 }
Chris@0 93 }
Chris@0 94
Chris@0 95 /**
Chris@0 96 * Handles a 401 error for HTML.
Chris@0 97 *
Chris@0 98 * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
Chris@0 99 * The event to process.
Chris@0 100 */
Chris@0 101 public function on401(GetResponseForExceptionEvent $event) {
Chris@0 102 $this->makeSubrequest($event, '/system/401', Response::HTTP_UNAUTHORIZED);
Chris@0 103 }
Chris@0 104
Chris@0 105 /**
Chris@0 106 * Handles a 403 error for HTML.
Chris@0 107 *
Chris@0 108 * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
Chris@0 109 * The event to process.
Chris@0 110 */
Chris@0 111 public function on403(GetResponseForExceptionEvent $event) {
Chris@0 112 $this->makeSubrequest($event, '/system/403', Response::HTTP_FORBIDDEN);
Chris@0 113 }
Chris@0 114
Chris@0 115 /**
Chris@0 116 * Handles a 404 error for HTML.
Chris@0 117 *
Chris@0 118 * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
Chris@0 119 * The event to process.
Chris@0 120 */
Chris@0 121 public function on404(GetResponseForExceptionEvent $event) {
Chris@0 122 $this->makeSubrequest($event, '/system/404', Response::HTTP_NOT_FOUND);
Chris@0 123 }
Chris@0 124
Chris@0 125 /**
Chris@0 126 * Makes a subrequest to retrieve the default error page.
Chris@0 127 *
Chris@0 128 * @param \Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent $event
Chris@0 129 * The event to process.
Chris@0 130 * @param string $url
Chris@0 131 * The path/url to which to make a subrequest for this error message.
Chris@0 132 * @param int $status_code
Chris@0 133 * The status code for the error being handled.
Chris@0 134 */
Chris@0 135 protected function makeSubrequest(GetResponseForExceptionEvent $event, $url, $status_code) {
Chris@0 136 $request = $event->getRequest();
Chris@0 137 $exception = $event->getException();
Chris@0 138
Chris@0 139 try {
Chris@0 140 // Reuse the exact same request (so keep the same URL, keep the access
Chris@0 141 // result, the exception, et cetera) but override the routing information.
Chris@0 142 // This means that aside from routing, this is identical to the master
Chris@0 143 // request. This allows us to generate a response that is executed on
Chris@0 144 // behalf of the master request, i.e. for the original URL. This is what
Chris@0 145 // allows us to e.g. generate a 404 response for the original URL; if we
Chris@0 146 // would execute a subrequest with the 404 route's URL, then it'd be
Chris@0 147 // generated for *that* URL, not the *original* URL.
Chris@0 148 $sub_request = clone $request;
Chris@0 149
Chris@0 150 // The routing to the 404 page should be done as GET request because it is
Chris@0 151 // restricted to GET and POST requests only. Otherwise a DELETE request
Chris@0 152 // would for example trigger a method not allowed exception.
Chris@0 153 $request_context = clone ($this->accessUnawareRouter->getContext());
Chris@0 154 $request_context->setMethod('GET');
Chris@0 155 $this->accessUnawareRouter->setContext($request_context);
Chris@0 156
Chris@0 157 $sub_request->attributes->add($this->accessUnawareRouter->match($url));
Chris@0 158
Chris@0 159 // Add to query (GET) or request (POST) parameters:
Chris@0 160 // - 'destination' (to ensure e.g. the login form in a 403 response
Chris@0 161 // redirects to the original URL)
Chris@0 162 // - '_exception_statuscode'
Chris@0 163 $parameters = $sub_request->isMethod('GET') ? $sub_request->query : $sub_request->request;
Chris@0 164 $parameters->add($this->redirectDestination->getAsArray() + ['_exception_statuscode' => $status_code]);
Chris@0 165
Chris@0 166 $response = $this->httpKernel->handle($sub_request, HttpKernelInterface::SUB_REQUEST);
Chris@0 167 // Only 2xx responses should have their status code overridden; any
Chris@0 168 // other status code should be passed on: redirects (3xx), error (5xx)…
Chris@0 169 // @see https://www.drupal.org/node/2603788#comment-10504916
Chris@0 170 if ($response->isSuccessful()) {
Chris@0 171 $response->setStatusCode($status_code);
Chris@0 172 }
Chris@0 173
Chris@14 174 // Persist the exception's cacheability metadata, if any. If the exception
Chris@14 175 // itself isn't cacheable, then this will make the response uncacheable:
Chris@14 176 // max-age=0 will be set.
Chris@14 177 if ($response instanceof CacheableResponseInterface) {
Chris@14 178 $response->addCacheableDependency($exception);
Chris@14 179 }
Chris@14 180
Chris@0 181 // Persist any special HTTP headers that were set on the exception.
Chris@0 182 if ($exception instanceof HttpExceptionInterface) {
Chris@0 183 $response->headers->add($exception->getHeaders());
Chris@0 184 }
Chris@0 185
Chris@0 186 $event->setResponse($response);
Chris@0 187 }
Chris@0 188 catch (\Exception $e) {
Chris@0 189 // If an error happened in the subrequest we can't do much else. Instead,
Chris@0 190 // just log it. The DefaultExceptionSubscriber will catch the original
Chris@0 191 // exception and handle it normally.
Chris@0 192 $error = Error::decodeException($e);
Chris@0 193 $this->logger->log($error['severity_level'], '%type: @message in %function (line %line of %file).', $error);
Chris@0 194 }
Chris@0 195 }
Chris@0 196
Chris@0 197 }