class DefaultExceptionHtmlSubscriber

Same name and namespace in other branches
  1. 9 core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php \Drupal\Core\EventSubscriber\DefaultExceptionHtmlSubscriber
  2. 8.9.x core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php \Drupal\Core\EventSubscriber\DefaultExceptionHtmlSubscriber
  3. 10 core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php \Drupal\Core\EventSubscriber\DefaultExceptionHtmlSubscriber

Exception subscriber for handling core default HTML error pages.

Hierarchy

Expanded class hierarchy of DefaultExceptionHtmlSubscriber

File

core/lib/Drupal/Core/EventSubscriber/DefaultExceptionHtmlSubscriber.php, line 18

Namespace

Drupal\Core\EventSubscriber
View source
class DefaultExceptionHtmlSubscriber extends HttpExceptionSubscriberBase {
    
    /**
     * The HTTP kernel.
     *
     * @var \Symfony\Component\HttpKernel\HttpKernelInterface
     */
    protected $httpKernel;
    
    /**
     * The logger instance.
     *
     * @var \Psr\Log\LoggerInterface
     */
    protected $logger;
    
    /**
     * The redirect destination service.
     *
     * @var \Drupal\Core\Routing\RedirectDestinationInterface
     */
    protected $redirectDestination;
    
    /**
     * A router implementation which does not check access.
     *
     * @var \Symfony\Component\Routing\Matcher\UrlMatcherInterface
     */
    protected $accessUnawareRouter;
    
    /**
     * Constructs a new DefaultExceptionHtmlSubscriber.
     *
     * @param \Symfony\Component\HttpKernel\HttpKernelInterface $http_kernel
     *   The HTTP kernel.
     * @param \Psr\Log\LoggerInterface $logger
     *   The logger service.
     * @param \Drupal\Core\Routing\RedirectDestinationInterface $redirect_destination
     *   The redirect destination service.
     * @param \Symfony\Component\Routing\Matcher\UrlMatcherInterface $access_unaware_router
     *   A router implementation which does not check access.
     */
    public function __construct(HttpKernelInterface $http_kernel, LoggerInterface $logger, RedirectDestinationInterface $redirect_destination, UrlMatcherInterface $access_unaware_router) {
        $this->httpKernel = $http_kernel;
        $this->logger = $logger;
        $this->redirectDestination = $redirect_destination;
        $this->accessUnawareRouter = $access_unaware_router;
    }
    
    /**
     * {@inheritdoc}
     */
    protected static function getPriority() {
        // A very low priority so that custom handlers are almost certain to fire
        // before it, even if someone forgets to set a priority.
        return -128;
    }
    
    /**
     * {@inheritdoc}
     */
    protected function getHandledFormats() {
        return [
            'html',
        ];
    }
    
    /**
     * Handles a 4xx error for HTML.
     *
     * @param \Symfony\Component\HttpKernel\Event\ExceptionEvent $event
     *   The event to process.
     */
    public function on4xx(ExceptionEvent $event) {
        if (($exception = $event->getThrowable()) && $exception instanceof HttpExceptionInterface) {
            $this->makeSubrequest($event, '/system/4xx', $exception->getStatusCode());
        }
    }
    
    /**
     * Handles a 401 error for HTML.
     *
     * @param \Symfony\Component\HttpKernel\Event\ExceptionEvent $event
     *   The event to process.
     */
    public function on401(ExceptionEvent $event) {
        $this->makeSubrequest($event, '/system/401', Response::HTTP_UNAUTHORIZED);
    }
    
    /**
     * Handles a 403 error for HTML.
     *
     * @param \Symfony\Component\HttpKernel\Event\ExceptionEvent $event
     *   The event to process.
     */
    public function on403(ExceptionEvent $event) {
        $this->makeSubrequest($event, '/system/403', Response::HTTP_FORBIDDEN);
    }
    
    /**
     * Handles a 404 error for HTML.
     *
     * @param \Symfony\Component\HttpKernel\Event\ExceptionEvent $event
     *   The event to process.
     */
    public function on404(ExceptionEvent $event) {
        $this->makeSubrequest($event, '/system/404', Response::HTTP_NOT_FOUND);
    }
    
    /**
     * Makes a subrequest to retrieve the default error page.
     *
     * @param \Symfony\Component\HttpKernel\Event\ExceptionEvent $event
     *   The event to process.
     * @param string $url
     *   The path/url to which to make a subrequest for this error message.
     * @param int $status_code
     *   The status code for the error being handled.
     */
    protected function makeSubrequest(ExceptionEvent $event, $url, $status_code) {
        $request = $event->getRequest();
        $exception = $event->getThrowable();
        try {
            // Reuse the exact same request (so keep the same URL, keep the access
            // result, the exception, et cetera) but override the routing information.
            // This means that aside from routing, this is identical to the master
            // request. This allows us to generate a response that is executed on
            // behalf of the master request, i.e. for the original URL. This is what
            // allows us to e.g. generate a 404 response for the original URL; if we
            // would execute a subrequest with the 404 route's URL, then it'd be
            // generated for *that* URL, not the *original* URL.
            $sub_request = clone $request;
            // The routing to the 404 page should be done as GET request because it is
            // restricted to GET and POST requests only. Otherwise a DELETE request
            // would for example trigger a method not allowed exception.
            $request_context = clone $this->accessUnawareRouter
                ->getContext();
            $request_context->setMethod('GET');
            $this->accessUnawareRouter
                ->setContext($request_context);
            $sub_request->attributes
                ->add($this->accessUnawareRouter
                ->match($url));
            // Add to query (GET) or request (POST) parameters:
            // - 'destination' (to ensure e.g. the login form in a 403 response
            //   redirects to the original URL)
            // - '_exception_statuscode'
            $parameters = $sub_request->isMethod('GET') ? $sub_request->query : $sub_request->request;
            $parameters->add($this->redirectDestination
                ->getAsArray() + [
                '_exception_statuscode' => $status_code,
            ]);
            $response = $this->httpKernel
                ->handle($sub_request, HttpKernelInterface::SUB_REQUEST);
            // Only 2xx responses should have their status code overridden; any
            // other status code should be passed on: redirects (3xx), error (5xx)…
            // @see https://www.drupal.org/node/2603788#comment-10504916
            if ($response->isSuccessful()) {
                $response->setStatusCode($status_code);
            }
            // Persist the exception's cacheability metadata, if any. If the exception
            // itself isn't cacheable, then this will make the response uncacheable:
            // max-age=0 will be set.
            if ($response instanceof CacheableResponseInterface) {
                $response->addCacheableDependency($exception);
            }
            // Persist any special HTTP headers that were set on the exception.
            if ($exception instanceof HttpExceptionInterface) {
                $response->headers
                    ->add($exception->getHeaders());
            }
            $event->setResponse($response);
        } catch (\Exception $e) {
            // If an error happened in the subrequest we can't do much else. Instead,
            // just log it. The DefaultExceptionSubscriber will catch the original
            // exception and handle it normally.
            $error = Error::decodeException($e);
            $this->logger
                ->log($error['severity_level'], Error::DEFAULT_ERROR_MESSAGE, $error);
        }
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title Overrides
DefaultExceptionHtmlSubscriber::$accessUnawareRouter protected property A router implementation which does not check access.
DefaultExceptionHtmlSubscriber::$httpKernel protected property The HTTP kernel.
DefaultExceptionHtmlSubscriber::$logger protected property The logger instance.
DefaultExceptionHtmlSubscriber::$redirectDestination protected property The redirect destination service.
DefaultExceptionHtmlSubscriber::getHandledFormats protected function Specifies the request formats this subscriber will respond to. Overrides HttpExceptionSubscriberBase::getHandledFormats
DefaultExceptionHtmlSubscriber::getPriority protected static function Specifies the priority of all listeners in this class. Overrides HttpExceptionSubscriberBase::getPriority 1
DefaultExceptionHtmlSubscriber::makeSubrequest protected function Makes a subrequest to retrieve the default error page.
DefaultExceptionHtmlSubscriber::on401 public function Handles a 401 error for HTML.
DefaultExceptionHtmlSubscriber::on403 public function Handles a 403 error for HTML. 1
DefaultExceptionHtmlSubscriber::on404 public function Handles a 404 error for HTML. 1
DefaultExceptionHtmlSubscriber::on4xx public function Handles a 4xx error for HTML.
DefaultExceptionHtmlSubscriber::__construct public function Constructs a new DefaultExceptionHtmlSubscriber. 1
HttpExceptionSubscriberBase::getSubscribedEvents public static function Registers the methods in this class that should be listeners. 1
HttpExceptionSubscriberBase::onException public function Handles errors for this subscriber. 1

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.