Aug 17 2015
Aug 17

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?

drupal8wide

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.

symfony event workflow

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.

Oct 20 2014
Oct 20

How to Build a Drupal 8 Module

Please be aware that due to the development process Drupal 8 has been undergoing at the time of writing, some parts of the code might be outdated. Take a look at this repository in which I try to update the example code and make it work with the latest Drupal 8 release.

With the incorporation of many Symfony components into Drupal in its 8th version, we are seeing a shift away from many Drupalisms towards more modern PHP architectural decisions. For example, the both loved and hated hook system is getting slowly replaced. Plugins and annotations are taking away much of the need for info hooks and the Symfony Event Dispatcher component is replacing some of the invoked hooks. Although they remain strong in Drupal 8, it’s very possible that with Drupal 9 (or maybe 10) hooks will be completely removed.

In this article we are going to primarily look at how the Symfony Event Dispatcher component works in Drupal. Additionally, we will see also how to invoke and then implement a hook in Drupal 8 to achieve similar goals as with the former.

To follow along or to get quickly started, you can find all the code we work with here in this repository. You can just install the module and you are good to go. The version of Drupal 8 used is the first BETA release so it’s preferable to use that one to ensure compatibility. Alpha 15 should also work just fine. Let’s dive in.

What is the Event Dispatcher component?

A very good definition of the Event Dispatcher component can be found on the Symfony website:

The EventDispatcher component provides tools that allow your application components to communicate with each other by dispatching events and listening to them.

I recommend reading up on that documentation to better understand the principles behind the event dispatcher. You will get a good introduction to how it works in Symfony so we will not cover that here. Rather, we will see an example of how you can use it in Drupal 8.

Drupal 8 and the Event Dispatcher

For the better part of this article, we will focus on demonstrating the use of the Event Dispatcher in Drupal 8. To this end, we will create a simple demo module (event_dispatcher_demo) that has a configuration form which saves two values as configuration. Upon saving this form, we will dispatch an event that contains the config object and which will allow other parts of the application to intercept and modify it before being saved. Finally, we will do just that by demonstrating how to subscribe (or listen) to these events.

In Drupal 7, this type of modularity is only achieved with hooks. Hooks are being invoked and modules have the option to implement them and contribute with their own data. At the end of this article, we will see how to do that as well in Drupal 8. But first, let’s get on with our demo module.

If you don’t know the basics of Drupal 8 module development, I recommend checking out my previous articles in this series.

The form

The first thing we need is a simple config form with two fields. In a file called DemoForm.php located in the src/Form folder, we have the following:

<?php

/**
 * @file
 * Contains Drupal\event_dispatcher_demo\Form\DemoForm.
 */

namespace Drupal\event_dispatcher_demo\Form;

use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;

class DemoForm extends ConfigFormBase {

  /**
   * {@inheritdoc}
   */
  public function getFormID() {
    return 'demo_form';
  }

  /**
   * {@inheritdoc}
   */
  public function buildForm(array $form, FormStateInterface $form_state) {
    $config = $this->config('event_dispatcher_demo.demo_form_config');
    $form['my_name'] = [
      '#type' => 'textfield',
      '#title' => $this->t('My name'),
      '#default_value' => $config->get('my_name'),
    ];
    $form['my_website'] = [
      '#type' => 'textfield',
      '#title' => $this->t('My website'),
      '#default_value' => $config->get('my_website'),
    ];
    return parent::buildForm($form, $form_state);
  }

  /**
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state) {

    parent::submitForm($form, $form_state);

    $config = $this->config('event_dispatcher_demo.demo_form_config');

    $config->set('my_name', $form_state->getValue('my_name'))
      ->set('my_website', $form_state->getValue('my_website'));
      
    $config->save();
  }

}

Let’s also create a route for it (in the event_dispatcher_demo.routing.yml file) so we can access the form in the browser:

event_dispatcher_demo.demo_form:
  path: 'demo-form'
  defaults:
    _form: '\Drupal\event_dispatcher_demo\Form\DemoForm'
    _title: 'Demo form'
  requirements:
    _permission: 'access administration pages'

So now if you point your browser to example.com/demo-form, you should see the form. Submitting it will create and persist a configuration object called event_dispatcher_demo.demo_form_config that contains two fields: my_name and my_website .

The event dispatcher

Now it’s time to work on the form submit handler (the formSubmit() method) and dispatch an event when the form is saved. This is what the new method will look like:

public function submitForm(array &$form, FormStateInterface $form_state) {

  parent::submitForm($form, $form_state);

  $config = $this->config('event_dispatcher_demo.demo_form_config');

  $config->set('my_name', $form_state->getValue('my_name'))
    ->set('my_website', $form_state->getValue('my_website'));

  $dispatcher = \Drupal::service('event_dispatcher');

  $e = new DemoEvent($config);

  $event = $dispatcher->dispatch('demo_form.save', $e);

  $newData = $event->getConfig()->get();

  $config->merge($newData);

  $config->save();
}

So what happens here? After we take the submitted values and add them to the config object like before, we retrieve the event dispatcher object from the service container:

$dispatcher = \Drupal::service('event_dispatcher');

Please keep in mind that it’s recommended you inject this service into your class, but for brevity, we will retrieve it statically. You can read this article about dependency injection and the service container for more information.

Then we create a new DemoEvent object and pass it the $config through its constructor (we have not yet created the DemoEvent class, we will do that in a minute). Next, we use the dispatcher to dispatch an event of our type and assign this action the identifier demo_form.save. This will be used when subscribing to events (we’ll see this later). The dispatch() method returns the event object with modifications made to it so we can retrieve the config values that may or may not have been altered elsewhere and merge them into our original configuration. Finally, we save this object like we did initially.

Before moving onto the event subscription part of our application, let’s create the DemoEvent class we just instantiated above. In a file called DemoEvent.php located in the src/ folder of our module, we have the following:

<?php

/**
 * @file
 * Contains Drupal\event_dispatcher_demo\DemoEvent.
 */

namespace Drupal\event_dispatcher_demo;

use Symfony\Component\EventDispatcher\Event;
use Drupal\Core\Config\Config;

class DemoEvent extends Event {

  protected $config;

  /**
   * Constructor.
   *
   * @param Config $config
   */
  public function __construct(Config $config) {
    $this->config = $config;
  }

  /**
   * Getter for the config object.
   *
   * @return Config
   */
  public function getConfig() {
    return $this->config;
  }

  /**
   * Setter for the config object.
   *
   * @param $config
   */
  public function setConfig($config) {
    $this->config = $config;
  }

} 

As you can see, this is a simple class that extends the default Event class and which defines setters and getters for the config object we will be passing around using this event. And since we created it, let’s also make sure we use it in the file where we defined the form:

use Drupal\event_dispatcher_demo\DemoEvent;

The event subscriber

Now that our form is functioning normally and an event is being dispatched when the form is saved, we should take advantage of that and subscribe to it. Let’s start with the event subscriber class that implements the EventSubscriberInterface. Inside a file called ConfigSubscriber.php (name of your choice) located in the src/EventSubscriber/ folder, we have the following:

<?php

/**
 * @file
 * Contains Drupal\event_dispatcher_demo\EventSubscriber\ConfigSubscriber.
 */

namespace Drupal\event_dispatcher_demo\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class ConfigSubscriber implements EventSubscriberInterface {

  static function getSubscribedEvents() {
    $events['demo_form.save'][] = array('onConfigSave', 0);
    return $events;
  }

  public function onConfigSave($event) {

    $config = $event->getConfig();

    $name_website = $config->get('my_name') . " / " . $config->get('my_website');
    $config->set('my_name_website', $name_website);
  }

}

So what happens here? The EventSubscriberInterface has only one required method called getSubscribedEvents(). This method is used to register events and callbacks to these events. So above we registered the callable onConfigSave() (found in the same class below) to the event dispatched with the identifier of demo_form.save. And in the callback method we simply add another value to the config object (based on a concatenation of the existing two values). The latter part is just for our demo purposes: here you can do what you want.

When we subscribed our onConfigSave() method to listen to the demo_form.save event, we passed a weight of 0. If you register multiple callbacks to the same event, this weight becomes important (the higher the number, the earlier it gets called). And if a callback alters the same values as one triggered before, they will get overridden. It’s good to keep this in mind.

Now in order for this event subscriber to work, we need to define it as a service and give it the event_subscriber tag. So in a file called event_dispatcher_demo.services.yml found in the root folder of our module, we will have this:

services:
  event_dispatcher_demo.config_subscriber:
    class: Drupal\event_dispatcher_demo\EventSubscriber\ConfigSubscriber
    tags:
      - { name: event_subscriber }

This is a simple service definition with the right tag that will make the container automatically instantiate an object of this class whenever the dispatcher is in play. And that is pretty much it. Clear the cache and if you now save the form again, the configuration object that gets saved will always contain a new value that is based on the first two.

Hooks

In the final part of this article we will demonstrate the use of hooks to achieve a similar goal.

First, let’s change the form submit handler and instead of dispatching events, we will invoke a hook and pass the config values to it. This is what the new submitForm() method will look like:

public function submitForm(array &$form, FormStateInterface $form_state) {

        parent::submitForm($form, $form_state);
        
        $config = $this->config('event_dispatcher_demo.demo_form_config');
        
        $config->set('my_name', $form_state->getValue('my_name'))
          ->set('my_website', $form_state->getValue('my_website'));
        
        $configData = $config->get();
        $newData = \Drupal::service('module_handler')->invokeAll('demo_config_save', array($configData));
        
        $config->merge($newData);
        
        $config->save();
}

We are not using any event objects nor the dispatcher service. Instead, we retrieve the Module Handler service that contains the invokeAll() method used to invoke hook implementations from all modules. This is essentially replacing the Drupal 7 module_invoke_all() helper. And again, it is recommended to inject this service, but for brevity, we’ll retrieve it statically.

The hook implementation invoked in our case is hook_demo_config_save and it gets one parameter, an array of values pulled from our config object. Inside $newData we will have an array of values merged from all the implementations of this hook. We then merge that into our config object and finally save it.

Let’s quickly see an example hook implementation. As with Drupal 7, these can only be in .module files:

/**
 * Implements hook_demo_config_save().
 */
function event_dispatcher_demo_demo_config_save($configValues) {

  $configValues['my_name_website'] = $configValues['my_name'] . " / " . $configValues['my_website'];

  return $configValues;

}

As you can see, we are adding a new value to the config array that will later be merged into the object getting persisted. And we have essentially the same thing as we did with the event dispatcher.

Conclusion

In this article we have taken a look at how the Symfony Event Dispatcher component works in Drupal 8. We’ve learned how flexible it makes our application when it comes to allowing others to extend functionality. Additionally, we’ve seen how the invoked hooks work in the new version of Drupal. Not much has changed since Drupal 7 in this respect apart from the frequency with which they are used. Many hooks have been replaced by plugins and annotations and the Event Dispatcher component has also taken on a big chunk of what was in D7 a hook responsibility.

Although the Event Dispatcher approach is more verbose, it is the recommended way to go forward. Where possible, we no longer use the old procedural approach characteristic to hooks but rather object oriented, decoupled and testable solutions. And Symfony helps greatly with that.

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web