Handle Me Gently: Inside the Drupal 8 Kernel

Handle Me Gently: Inside the Drupal 8 Kernel image

The heart of Drupal 8 is the DrupalKernel, an extension of the HttpKernel component from Symfony. The documentation beautifully summarizes its purpose:

The HttpKernel component provides a structured process for converting a Request into a Response by making use of the EventDispatcher component.

The HttpKernelInterface only provides a single method, handle(), which is where the seemingly magical process of converting a Request to Response takes place. Here is the code without comments:

<?php

   // vendor/symfony/http-kernel/HttpKernelInterface.php 
   
	namespace Symfony\Component\HttpKernel;

	use Symfony\Component\HttpFoundation\Request;
	use Symfony\Component\HttpFoundation\Response;

	interface HttpKernelInterface {
	   const MASTER_REQUEST = 1;
	   const SUB_REQUEST = 2;

	  public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true);
	 }

It is Request in, Response out. We receive an instance of Symfony\Component\HttpFoundation\Request class and return Symfony\Component\HttpFoundation\. When the HTTP Request is received by the index.php file, an instance of DrupalKernel is created as well as the Request. The handle() method of the DrupalKernel is then executed with the Request object as the argument. This method must return an instance of Symfony\Component\HttpFoundation\Response class.

// core/lib/Drupal/Core/DrupalKernel.php

public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {
    // Ensure sane PHP environment variables.
    static::bootEnvironment();

    try {
      $this->initializeSettings($request);

      // Redirect the user to the installation script if Drupal has not been
      // installed yet (i.e., if no $databases array has been defined in the
      // settings.php file) and we are not already installing.
      if (!Database::getConnectionInfo() && !drupal_installation_attempted() && PHP_SAPI !== 'cli') {
        $response = new RedirectResponse($request->getBasePath() . '/core/install.php', 302, ['Cache-Control' => 'no-cache']);
      }
      else {
        $this->boot();
        $response = $this->getHttpKernel()->handle($request, $type, $catch);
      }
    }
    catch (\Exception $e) {
      if ($catch === FALSE) {
        throw $e;
      }

      $response = $this->handleException($e, $request, $type);
    }

    // Adapt response headers to the current request.
    $response->prepare($request);

    return $response;
  }

In this method, up to 5 major things take place:

  1. Prepare the environment — bootEnvironment():
    There is a good number of Drupal-specific features, carried over from Drupal 7. We make sure the application root is correctly defined and some PHP settings in php.ini need to be overridden for Drupal to work properly. In this method, the application environment is configured for Drupal.

  2. Initialize the settings — initializeSettings():
    The old settings.php is located and initialized. With the aid of our autoloader got earlier in index.php, a singleton instance of Settings class is created from the contents of the settings file. Most importantly, the database connection information is initialized which will be used to determine whether Drupal has been installed or not.

  3. Check Drupal installation:
    A check is carried out with Database::getConnectionInfo() method for the database-related variables in settings.php which gets set during the installation process. Also, the drupal_installation_attempted() function in core\includes\bootstrap.inc is called, which determines whether the installation process has finished from the install tasks in the $GLOBALS array.

If Drupal has not been installed, the RedirectResponse object takes the application back to complete the installation process.

  1. Boot the application - boot():
    Drupal is already installed, so the stage needs to be fully set for all the classes and objects in the application. The file cache is configured, and the Service Container is built.

  2. Hand over request to HttpKernel - handle():
    The request is now passed to the handle() method of the HttpKernel class and a Response object is expected. Inside this method, apart from the X-Php-Ob-Level header being set and exceptions being taken care of, there is a call to handleRaw() method inside the same class.

     // vendor/symfony/http-kernel/HttpKernel.php
     
    
     private function handleRaw(Request $request, $type = self::MASTER_REQUEST) { 
    
       $this->requestStack->push($request);
    
       // request
       $event = new GetResponseEvent($this, $request, $type);
       $this->dispatcher->dispatch(KernelEvents::REQUEST, $event);
    
       if ($event->hasResponse()) {
         return $this->filterResponse($event->getResponse(), $request, $type);
       }
    
       // load controller
       if (false === $controller = $this->resolver->getController($request)) {
         throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". The route is wrongly configured.', $request->getPathInfo()));
        }
       
        $event = new FilterControllerEvent($this, $controller, $request, $type);
        $this->dispatcher->dispatch(KernelEvents::CONTROLLER, $event);
        $controller = $event->getController();
    
        // controller arguments
        $arguments = $this->resolver->getArguments($request, $controller);
    
        // call controller
        $response = call_user_func_array($controller, $arguments);
    
        // view
        if (!$response instanceof Response) {
          $event = new GetResponseForControllerResultEvent($this, $request, $type, $response);
          $this->dispatcher->dispatch(KernelEvents::VIEW, $event);
          if ($event->hasResponse()) {
            $response = $event->getResponse();
          }
    
        if (!$response instanceof Response) {
          $msg = sprintf('The controller must return a response (%s given).', $this->varToString($response));
           // the user may have forgotten to return something
           if (null === $response) {
               $msg .= ' Did you forget to add a return statement somewhere in your controller?';
           }
          
        throw new \LogicException($msg);
       } 
     }
     
     return $this->filterResponse($response, $request, $type);
     }        
    

The key concept here is the EventDispatcher Component which organizes the way application components communicate with each other through dispatched events. When certain events happen, the system broadcasts them, and listeners that have signified interest in those events take action accordingly:

1. The request is pushed on to a stack and the `KernelEvents::REQUEST` is dispatched.
2. The request is handed over to a resolver which is an object that knows how to determine the controller to execute based on a given request. In Drupal, this is where the route definition comes into play here. The current route is matched to only one controller and the `KernelEvents::CONTROLLER` event is dispatched.
3. The arguments required by the controller are extracted from the request and passed to the call to execute the controller method.
4. If a valid response is returned, the `KernelEvents::VIEW` event is dispatched.
5. Finally, the response is filtered and returned to the `handle()` method in the same class.
  1. Prepare the response:
    The HTTP version in the status line (the first line of the response) needs to match the corresponding request line. Then the response headers are tweaked to ensure they match what is expected based on the request. These include Content-Type, Content-Length, Transfer-Encoding, Pragma and Expires.

The response is now ready for the next phase of the journey back to the browser or whatever user agent initiated the request in the first place.

Conclusion

The Symfony Components in Drupal 8 draw closer attention to the foundation of the web which is the HTTP message protocol. When a request message reaches the server, it can do either of the following: Interpret the request and map it to a resource which it returns to the client. If the mapped resource is a program, it is executed, and the output is returned to the client. If the request cannot be satisfied, an error message is returned.

We have taken a closer look at part of how Drupal maps requests to the right program for execution with the help of some key Symfony components and returns a response.

KEEP MOVING FORWARD

Deji Akala / drupal