Using lazy builders with Twig templates

By joachim, Thu, 06/04/2020 - 14:04

Lazy builders were introduced in Drupal 8's render system to solve the problem of pieces of your page that have a low cacheability affecting the cacheability of the whole page. Typical uses of this technique are with entire render arrays which are handed over to a lazy builder.

However, in retrofitting an existing site to make use of caching more effectively, I found that we have a large amount of content that would be cacheable were it not for a single string. Specifically, we have a DIV of the details for a product, but also in that DIV there is the serial number for the user's specific instance of that product.

The whole of this product DIV was output by a Twig template. I could have ripped this up and remade it as a big render array, putting the lazy builder in for the serial number, but this seemed like a lot of work, and would also make our front-end developers unhappy.

It turned out there was another way. The render system deals with placeholders for lazy builders automatically, so you typically will just do:

$build['uncacheable_bit'] = [
  '#lazy_builder' => [
    'my_service:myLazyBuilder',
    [],
  ],
];

but as I learned from a blog post by Borisson, you can make the placeholders yourself put them anywhere in the content and attach the lazy builders to the build array. This is what the render system does when it handles a '#lazy_builder' builder render array item.

One application of this is that you can pass the placeholder as a parameter to a twig template:

// Your placeholder can be anything, but using a hash like the render system
// does prevents accidental replacements.
$placeholder = Crypt::hashBase64('some_data');

$build['my_content'] = [
  '#theme' => 'my_template',
  '#var_1' => $var_1,
  // This is just a regular parameter as far as the Twig template cares, 
  // output with {{lazy_builder_var}} as normal.
  '#lazy_builder_var' => $placeholder,
];

$build['#attached']['placeholders'][$placeholder] = [
  '#lazy_builder' => [
    // The lazy builder callback and parameters.
    'my_service:myLazyBuilder',
    [],
  ],
];

With this approach, the Twig template just needed some minor changes. It doesn't care about the lazy builder; all it sees is the placeholder string which it treats like any other text. The render system finds the placeholder in the '#attached' attribute, and handles the replacement when the rendered template is retrieved from the render cache.

The result is that we're able to cache much more in the render cache, and improve site performance.

Now I just have to figure out the cache tags...