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:
-
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 inphp.ini
need to be overridden for Drupal to work properly. In this method, the application environment is configured for Drupal. -
Initialize the settings —
initializeSettings()
:
The oldsettings.php
is located and initialized. With the aid of our autoloader got earlier inindex.php
, a singleton instance ofSettings
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. -
Check Drupal installation:
A check is carried out withDatabase::getConnectionInfo()
method for the database-related variables insettings.php
which gets set during the installation process. Also, thedrupal_installation_attempted()
function incore\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.
-
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. -
Hand over request to
HttpKernel
-handle()
:
The request is now passed to thehandle()
method of theHttpKernel
class and aResponse
object is expected. Inside this method, apart from theX-Php-Ob-Level
header being set and exceptions being taken care of, there is a call tohandleRaw()
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.
- 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 includeContent-Type
,Content-Length
,Transfer-Encoding
,Pragma
andExpires
.
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.