From Request to Response: A Journey into Drupal 8 Internals
In the first article on Drupal 8 module development we looked a bit at the routing aspect of this process. We’ve seen that creating pages with paths is now a matter of declaring routes that match up with controllers. The latter, as we’ve seen, can return a render array that gets interpreted into markup and displayed in the main content area of that page. However, did you know that under the hood, Drupal actually transforms that array into a Response object according to the dictates of Symfony’s HTTPKernelInterface?
In this article, I would like us to go deeper into the internals of Drupal 8 (and Symfony2) and look at what actually happens (and can happen) from the moment a request is made by a user to the one in which they see something returned in response. The example I mentioned above is just one direction this process can go in, and today we are also going to see other possibilities. The goal is to understand the flexibility of the system which in turn can help us build awesome applications.
Before going into it, I strongly recommend you check out this diagram which does an amazing job at synthesizing what is often referred to as the render pipeline. Though in my opinion it represents more than the name implies because the render system is only part of what’s depicted, albeit a big one.
Front controller (index.php)
It’s no secret that Symfony2 is now an important part of Drupal. The latter uses many of Symfony’s components, most importantly for this article the HTTPKernel and HTTPFoundation ones. Together they are responsible for encapsulating a user request, passing it to the application and then returning whatever comes back to the user in a consistent and OO way.
The HTTPKernelInterface (something you probably heard about also from other contexts) is what glues all of this together by taking in a Request object and always returning a Response one. A very simple but powerful concept.
This process is initiated inside the index.php
file which starts by generating said Request object and passing it to the HTTPKernel::handle()
method. The latter is then responsible for returning a Response object. At a high level, this is what happens both in a Drupal application as well as in a Symfony one (or any other that leverages the HTTPKernel component).
HTTPKernel and events
HTTPKernel is the heart of any Symfony based application. Its handle()
method, as we saw, has a great deal of responsibility in preparing a response and it does so in a workflow driven by events. This makes for a very flexible application where the heavy lifting is always delegated to listeners of these events.
If you look at the diagram from earlier, you can see this workflow depicted in the second column, and it essentially represents the glue between Symfony and the Drupal side of things.
It starts with the first event called kernel.request
. Subscribers to this event handle various tasks. But two very important ones in Drupal 8 are the format negotiation and routing. The first determines the type of response that needs to be returned (html, json, image, pdf, etc) while the second determines what the code responsible for handling this is (the _controller
key of a route definition inside the routing.yml
file). But like in most steps in this event workflow, if a listener returns a response object, the process skips most of the further steps (stops propagation) and goes straight to kernel.response
.
The second event is kernel.controller
which is called after the application knows which controller is responsible for handling the request. At this point, listeners can still perform some overriding operations on it. Closely following this step, the Kernel is responsible for resolving the arguments that get passed to the controller. One such operation in Drupal is loading objects based on IDs found in the request (for example nodes) and directly providing the controller with them. Then finally the controller gets called with the respective parameters.
The controller is responsible for returning a response of some kind. If it returns a Response
object, the process skips to the kernel.response
event. Listeners to the latter can perform last minute modifications on the object such as modifying headers or the content itself. And after getting it from the handle()
method, the front controller uses the send()
method on the Response
object to send it back to the user and terminates the process.
Going deeper with render arrays
If the controller does not return a Response
object, the Kernel fires one last event: kernel.view
. Its subscribers are responsible for turning the result of the controller into an actual Response
object. So this means that you have the option of returning from your controller any kind of object as long as you couple it with a VIEW event subscriber that turns that into a proper Response
.
However, as we’ve seen in the example, most of the time controllers will return a render array. Usually this represents the page’s main content (similar to page callbacks in Drupal 7).
To handle this, Drupal 8 has a MainContentViewSubscriber responsible for transforming this array into proper Response
objects. It does so by using a particular MainContentRenderer chosen during the format negotiation phase we’ve talked about before. And although there are a few such renderers already available, the default one used is the HtmlRenderer.
HTMLRenderer
Since this is the most commonly used type of main content renderer, let’s go in a bit deeper and see how this builds the page.
One of the cool things about this step in the process is the concept of page variants.
This means that HTMLRenderer
dispatches an event responsible for finding out which type of page is to be used to wrap the main content render array: RenderEvents::SELECT_PAGE_DISPLAY_VARIANT
. By default, the SimplePageVariant is used unless the Block module is enabled. In that case the BlockPageVariant kicks in and allows the placement of the blocks in the regions around the main content. If you want, you can subscribe to this event in your own module and provide your own variant.
All of this happens within the prepare()
method of the HTMLRenderer
which supplies the renderResponse()
method with a #type => 'page'
render array that wraps the main content one. The latter two get in turn wrapped into a #type => 'html'
render array which gets finally rendered using the Renderer class (the equivalent of drupal_render()
in Drupal 7). The resulting HTML string gets added to the Response
object and gets returned to the front controller.
Although this is a very high level overview of the process, this is basically what happens. Now we have a Response
object which means the Kernel can dispatch its kernel.response
event. And right after this, the front controller can send the Response
back to the user and terminate the process.
Conclusion
In this article we’ve taken a journey into Drupal 8 (and Symfony2) internals by following the pipeline from a user request to the response the server returns. We’ve seen how Drupal 8 leverages the HTTPKernel and HTTPFoundation Symfony2 components and how it basically lives on top of them. Additionally, we’ve seen how the glue between them is made up of the events the Kernel dispatches to which Drupal subscribes for all of its functionality. Finally, we’ve seen how HTML pages are built and returned to the user with the help of the render pipeline.
I believe that understanding what is going on under the hood in a Drupal 8 application will allow you to create awesome applications by knowing exactly which entry points you have into this flow. And I believe that if you take away only one thing from this article, it should be the word flexibility. Because the flexibility for building exactly what we need in Drupal 8 far surpasses anything in Drupal 7. It has truly become modern.