Drupal implements the Front Controller design pattern. All HTTP requests are directed to the index.php file, and a response is generated and returned.
With the architectural decision to use Object-Oriented Programming and Symfony Components, the file has remained very short and concise. However, the request and response aspects of the process are given prominence. This is the index.php without comments:

<?php

use Drupal\Core\DrupalKernel;
use Symfony\Component\HttpFoundation\Request;

$autoloader = require_once 'autoload.php';

$kernel = new DrupalKernel('prod', $autoloader);

$request = Request::createFromGlobals();
$response = $kernel->handle($request);

$response->send();

$kernel->terminate($request, $response);

DrupalKernel extends the HttpKernel to add some Drupal-specific functionality to the Kernel. However, the HttpKernelInterface has only one method — handle() — which deals with the conversion of Request to Response. After the response has been sent, the Kernel calls the terminate() method, which performs some tasks at the end of the request.

We are now going to explore what happens in this method.

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

public function terminate(Request $request, Response $response) {
    // Only run terminate() when essential services have been set up properly
    // by preHandle() before.
    if (FALSE === $this->prepared) {
      return;
    }

    if ($this->getHttpKernel() instanceof TerminableInterface) {
      $this->getHttpKernel()->terminate($request, $response);
    }
  }

It is possible that nothing happens, but in most cases, the terminate() method of the HttpKernel class will be called. It looks like this:

// vendor/symfony/http-kernel/HttpKernel.php

public function terminate(Request $request, Response $response)
{
    $this->dispatcher->dispatch(KernelEvents::TERMINATE, new PostResponseEvent($this, $request, $response));
}

This is part of the Symfony EventDispactcher Component. All that happens here is the KernelEvents::TERMINATE event is dispatched. Nothing more, nothing less. However, other parts of the application that have subscribed to this event will be listening for it. When the broadcast gets to them, they act accordingly.

When event subscribers get registered, they may have a priority, and if none is specified, it defaults to 0. The registered methods will now be executed according to priority — a higher value means it is more important, and if two have the same, the one that is added to the dispatcher first gets executed first. There are 5 key listeners for this event.

  1. core/modules/user/src/EventSubscriber/UserRequestSubscriber.php class. (Priority: 300):

    In the onKernelTerminate() method of this class, the current user's last access time is updated. Usually, there will be lots of requests, so we ensure this update is done every 180 seconds or more, and this interval is configurable as session_write_interval in settings.php.

  2. core/lib/Drupal/Core/EventSubscriber/PathSubscriber (Priority: 200):

    Another onKernelTerminate() method here makes sure we cache the system paths for the request.

  3. core/lib/Drupal/Core/EventSubscriber/RequestCloseSubscriber (Priority: 100):

    This invokes the onTerminate() method which performs the cache writing as part of the request ending.

  4. core/modules/automated_cron/src/EventSubscriber/AutomatedCron (Priority: 100):

    If automated cron is enabled, it is run in the onTerminate() method here.

  5. core/lib/Drupal/Core/EventSubscriber/KernelDestructionSubscriber (Priority: -100):

    The onKernelTerminate() method here is designed to be called last as it is the lowest priority. If the service container was initialized during this request, this is where it is destroyed.

The concept of performing these tasks at the end of a request is not a new thing. In Drupal 7 this is what the drupal_page_footer() function does. Here is the code:

// includes/common.inc

/**
 * // Performs end-of-request tasks.
 *
 * This function sets the page cache if appropriate, and allows modules to
 * react to the closing of the page by calling hook_exit().
 */
function drupal_page_footer() {
  global $user;

  module_invoke_all('exit');

  // Commit the user session, if needed.
  drupal_session_commit();

  if (variable_get('cache', 0) && ($cache = drupal_page_set_cache())) {
    drupal_serve_page_from_cache($cache);
  }
  else {
    ob_flush();
  }

  _registry_check_code(REGISTRY_WRITE_LOOKUP_CACHE);
  drupal_cache_system_paths();
  module_implements_write_cache();
  drupal_file_scan_write_cache();
  system_run_automated_cron();
}

It invokes hook_exit on all modules, writes the user session, caches system paths, writes cache and runs automated cron. This is more or less what happens when the KernelEvents::TERMINATE event is dispatched.

Conclusion

Drupal 8 may seem overly complex but, in reality, it is not hard to understand. In most cases, we are solving the same old problems in a different and possibly better-structured way. This is exemplified by the terminate() method of the DrupalKernel class, which is very similar to the old drupal_page_footer() function in Drupal 7.

The knowledge gained by learning Drupal 8 can be of benefit in other applications built on similar Symfony components, for example Laravel and Sylius.