6

I have a module that in hook_preprocess() checks with drupal_get_http_header() whether Drupal is returning a 403 or 404 error, but in Drupal 8 that function is deprecated, since header handling is being shifted to a Symfony response object.

The used code is similar to the following one.

$header = drupal_get_http_header('Status');

switch ($header) {
  case '403 Forbidden':
    // Add new headers.
    // …
    break;

  case '404 Not Found':
    // Add new headers.
    // …
    break;
}

What code should I use in Drupal 8? If that means to use a different hook, that is fine.

I would also be interested in the case I need to add HTML output to the page. I guess that in this case I should use hook_page_alter(), but I still don't know how to access the response object for the page being served.

1

2 Answers 2

4

You can subscribe to the Symfony KernelEvents::RESPONSE event:

The RESPONSE event occurs once a response was created for replying to a request

This event allows you to modify or replace the response that will be replied. The event listener method receives a Symfony\Component\HttpKernel\Event\FilterResponseEvent instance.

To do so, create a new module ("response_test" in this example), with the usual .info.yml and .module file.

Then register your event subscriber in the module's services file.

response_test.services.yml

services:
    response_test.response_subscriber:
      class: Drupal\response_test\ResponseSubscriber
      tags:
        - { name: event_subscriber }

Now all you need is the class to handle the event.

lib/Drupal/response_test/ResponseSubscriber.php (src/ResponseSubscriber.php in latest Drupal 8 versions)

namespace Drupal\response_test;

use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class ResponseSubscriber implements EventSubscriberInterface {
  
  public function onRespond(FilterResponseEvent $event) {
    if ($event->getRequestType() !== HttpKernelInterface::MASTER_REQUEST) {
      return;
    }
    
    $response = $event->getResponse();
    if ($response->getStatusCode() == 404) {
      // Prepare a new Symfony\Component\HttpFoundation\Response and use
      $event->setResponse($new_response);

      // or call one of the setter methods on $response, the changes will persist
    }
  }
  
  public static function getSubscribedEvents() {
    $events[KernelEvents::RESPONSE][] = array('onRespond');
    return $events;
  }
  
}

I'm a little unsure about changing the HTML content in this context; you have access to $response->getContent() and $response->setContent() so you can manipulate the raw string directly, but as far as I can tell everything's already rendered at this point.

3
  • Should lib/Drupal/response_test/ResponseSubscriber.php be src/EventSubscriber/ResponseSubscriber.php ? Commented Jun 21, 2021 at 22:08
  • Curious, because I referenced your answer here: drupal.stackexchange.com/a/303690 (Great answer, BTW) Commented Jun 21, 2021 at 22:09
  • 1
    @ChrisHappy On earlier versions, Drupal didn't follow PSR-4; that's why the class file is said to be in that directory. Event subscriber classes aren't required to be in the EventSubscriber directory, although that is the directory usually used from Drupal core modules.
    – apaderno
    Commented Jun 22, 2021 at 7:19
3

To check for error codes you can use the following snippet. It won't work for 200 status codes though. Since there's no exception.

use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;

$exception = \Drupal::requestStack()->getCurrentRequest()->attributes->get('exception');
if ($exception instanceof HttpExceptionInterface) {
  $status_code = $exception->getStatusCode();
  // ...
}

I'm unsure if it's safe to assume that the status code must be 200 if there's no exception in the first place. If yes you could use the following snippet to cover a 200er response.

if (!\Drupal::requestStack()->getCurrentRequest()->attributes->has('exception')) {
  $status_code = '200';
}
8
  • The request attribute exception is only present in a subrequest, which Drupal uses to build a themed 403/404 response with configurable content. The standard exception handling, which takes place in the main request, does not use this attribute. The status code is accessible in the exception thrown and once handled by KernelEvents::EXCEPTION in the response. Which don't necessarily contain the same status code, the exception subscriber can put in the response what it wants.
    – 4uk4
    Commented Nov 16, 2019 at 11:08
  • See for example drupal.stackexchange.com/questions/231195/…
    – 4uk4
    Commented Nov 16, 2019 at 11:15
  • @4k4 – So using this snippet if there's an exception the status code returned always only can be 403 or 404, right? So the assumption that if no exception is returned the status code must be 200 is not entirely safe, right? And to always get the current request's status code I'd need an event subscriber? That I can create a service for and inject it where I want to get the status code then?
    – leymannx
    Commented Nov 17, 2019 at 11:57
  • 1
    @4k4 – Ah I see I think I'm starting to get it now. This subrequest is necessary for theming either a 403 or 404 response and this status code info/method is just there to differentiate. And for #2245767 this still can make sense but that "200" is not totally correct. The option naming and the condition itself might need to be renamed, too.
    – leymannx
    Commented Nov 17, 2019 at 15:45
  • 1
    Link /admin/config/system/site-information and explain the visibility is for these configurable error pages and the options in this context would then be "403 page" "404 page" and "no error page"
    – 4uk4
    Commented Nov 17, 2019 at 16:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.