Lazy builders

Lazy builders are nothing more than callbacks on a render array that Drupal can use to build the render array at a later stage. The callbacks can be static (a reference to a class and method) or dynamic (a reference to a service and method). Using the latter approach is more flexible as we can inject dependencies from the container as we do regularly with services. Moreover, the callback can take parameters, which means it can build the render array already having at least part of the required data.

The best way to understand this is to see an example. Since we decided that our salutation component should have a cache lifetime of 0 seconds, it's a good opportunity to build it using a lazy builder.

The first thing we need to do is replace our helloWorld Controller method in which we directly call the salutation service with this:

return [ 
  '#lazy_builder' => ['hello_world.lazy_builder:renderSalutation', []], 
  '#create_placeholder' => TRUE, 
]; 

Back in Chapter 4, Theming, when I said a render array needs to have at least one of the four properties (#type, #theme, #markup, or #plain_text), I lied. We can also use a lazy builder like this to defer the building of the render array to a later stage.

The #lazy_builder needs to be an array whose first item is the callback and second is an array of arguments to pass to it. In our case, we don't need any of the latter. We could pass the salutation service, but instead, we will inject it into the new hello_world.lazy_builder service we will create in a minute. The callback reference is in the format of service_name:method (one colon used for separation) or for static calls class_name::method (two colons). We also explicitly declare #create_placeholder to make it clear that this render array should be replaced with a placeholder. Lastly, as I mentioned earlier, the cache metadata can be applied to this render array or it can also be on the resulting one from the lazy builder. So, we'll opt for the latter approach in this case.

Let's now define our service:

hello_world.lazy_builder: 
  class: Drupal\hello_world\HelloWorldLazyBuilder 
  arguments: ['@hello_world.salutation']  

Nothing out of the ordinary here, but we are injecting the HelloWorldSalutation service as a dependency so that we can ask it for our salutation component. The actual service class looks like this:

namespace Drupal\hello_world; 
 
/** 
 * Lazy builder for the Hello World salutation. 
 */ 
class HelloWorldLazyBuilder { 
 
  /** 
   * @var \Drupal\hello_world\HelloWorldSalutation 
   */ 
  protected $salutation; 
 
  /** 
   * HelloWorldLazyBuilder constructor. 
   * 
   * @param \Drupal\hello_world\HelloWorldSalutation $salutation 
   */ 
  public function __construct(HelloWorldSalutation $salutation) { 
    $this->salutation = $salutation; 
  } 
 
  /** 
   * Renders the Hello World salutation message. 
   */ 
  public function renderSalutation() { 
    return $this->salutation->getSalutationComponent(); 
  } 
}  

All very simple. The renderSalutation() method is required as we referenced it from our lazy builder. That is all we have to do. But, what exactly happens with this?

When Drupal renders our Controller, it finds the lazy builder and registers it with a placeholder, which is then used instead of the actual final render array. Then, at a much later stage in the page-building process, the lazy builder is invoked and the actual output is rendered to replace the placeholder. There are a couple of advantages and implications with this. First, it allows Drupal to bypass this highly dynamic bit of output and cache the rest of the components in the dynamic page cache. This is to prevent the lack of cacheability from infecting the entire page. Second, there are two different strategies (so far) with which placeholders can be processed. By default, in using the so-called Single Flush method, the placeholder replacement is postponed until the last minute, but the response is not sent back to the browser before this is done. So, the dynamic page cache does improve things (caches what it can), but the response still depends on the placeholder processing finishing. Depending on how long that takes, the page load, in general, can suffer. However, when using the BigPipe (https://www.facebook.com/notes/facebook-engineering/bigpipe-pipelining-web-pages-for-high-performance/389414033919) approach, the response is sent back to the browser before the placeholders are replaced. And as the latter finishes as well, the replacements are streamed to the browser. This greatly improves the perceived performance of the site as users can already see most parts of the page before the slower bits appear.

The BigPipe technique was invented by Facebook as a way to deal with highly dynamic pages and was gradually brought into Drupal 8 as an experimental core module. With version 8.3 it has been marked stable and ready for use in production sites. I highly recommend you keep this module enabled as it comes with the Standard installation profile.

As you've probably guessed by now, the lazy builder approach is only useful when it comes to Dynamic Page Caching. That is when we cache for authenticated users. It will not work with the Internal Page Cache which is used for anonymous users.