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
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
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.
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.
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.
PHP Authorization with JWT (JSON Web Tokens)
By Miguel Ibarra Romero, Matthew Setter,
Need to authenticate requests to a server? Learn what JWTs are and how to use them instead of sessions to authenticate your users via API calls.
Setting Up a Modern PHP Development Environment with Docker
By Tom Butler,
There are so many ways to set up your PHP development environment, but using Docker is the current best practice. Let's walk through how to do it properly.
Getting Started with Laravel Livewire
By Wern Ancheta,
Daniel Sipos is a Drupal developer who lives in Brussels, Belgium. He works professionally with Drupal but likes to use other PHP frameworks and technologies as well. He runs webomelette.com, a Drupal blog where he writes articles and tutorials about Drupal development, theming and site building.