To be able to dynamically provide full-width sections I basically use a simple CSS toolkit. It operates with a classic .container CSS class that defines the common width, a .container__wrapper class that makes all of its children like a container and a .container__reset class that resets the above, so the element reseted will fill the entire available width.

The problem

The main scope of the toolkit described above is the content region. When I add the .container__wrapper class to that region’s attributes, I don’t have to explicitly use the .container CSS class for all blocks inside it: I only have to reset those sections that I want to make full-width.

But what happens if the user gets a client error (403, 404)? Well, the site will look like this:

The client error (4xx) message is out of the container canvas The client error (4xx) message is out of the container canvas

To solve this issue we have many options:

  • We may use special nodes as 403/404 messages whose layout can be easily overridden and customized in the theme.
  • We may use Page Manager pages with a condition plugin.

But I think that all of these make our Drupal site overcomplicated and more difficult to maintain. I’m totally satisfied with the original messages – those are already translated to many languages. So I chose a third option.

Extending the controller of the client error pages

Well, since I just needed a div wrapper around the original messages I extended the original controller and changed the controller of the client error routes to the new one.

So first, I had to create the new controller with an additional method that provides the needed theme_wrapper and adds the wrapper to the output of the original methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php

use Drupal\system\Controller\Http4xxController as DefaultHttp4xxController;

/**
 * Customizable default HTTP 4xx response templates.
 */
class Http4xxController extends DefaultHttp4xxController {

  /**
  * Returns theme wrappers for Http 4xx response messages.
  *
  * @param string $error_code
  *   Status code of the message.
  *
  * @return array
  *   A renderable array with a theme_wrapper.
  */
  public function http4xxWrapper($error_code = '4xx') {
    return [
      '#theme_wrappers' => [
        'container__http4xx' => [
          '#attributes' => [
            'class' => ['system-4xx', 'system-4xx--' . $error_code],
          ],
        ],
      ],
    ];
  }

}

Of course, I had to repeat all of the original methods:

1
2
3
public function on4xx() {
  return parent::on4xx() + $this->http4xxWrappers();
}

After that I had this new controller, I only had to create the route subscriber that changes the original controller to my new one.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Component\Routing\RouteCollection;

/**
 * Listens to the dynamic route events.
 */
class Http4xxRouteSubscriber extends RouteSubscriberBase {

  /**
   * {@inheritdoc}
   */
  public function alterRoutes(RouteCollection $collection) {
    // Repeat this for every case you want to customize.
    if (
      $route = $collection->get('system.4xx') &&
      $route->getDefault('_controller') === '\Drupal\system\Controller\Http4xxController::on4xx')
    {
      $route->setDefault('_controller', '\Drupal\http4xx\Controller\StHttp4xxController::on4xx');
    }
  }

}

At last I only had to make Drupal be able to discover my event subscriber by naming and configuring my service (http4xx.services.yml):

1
2
3
4
services:
  http4xx.route_subscriber:
    class: Drupal\http4xx\Routing\RouteSubscriber
    tags: [{name: event_subscriber}]

Summary

With the solution explained above we are able to add and to control the template of the original client error messages. We don’t have to rely on other (possibly fragile or overcomplicated) solutions, and despite using custom code, it’s pretty sure that it will work as expected, and it’s also obvious that it will be easy to maintain.