Skip to main content

The cache system in Drupal 9 delivers the API with the elements required for creation, storage and invalidation of cached data.  Drupal is structured so initially data is stored in the database.  Whereas, files are stored in the following directories:

sites/default/files/css
sites/default/files/js
sites/default/files/php

Focusing on database storage, from a performance perspective the default cache is managed via the admin interface

admin/config/development/performance

On this page, you want to set the bandwidth optimization so both css and js are checked.  Simply meaning, that the respective CSS and JS files rather than being shown individually, two files will be generated.  One with the aggregated CSS and the other JS files.

Outside of the default Drupal cache option, other considerations are:

  • Memcache
  • Redis

 

Custom development

If you are not planning to develop a custom theme or module then this is as far as you need to read.  Otherwise, let's keep discovering.

To begin, what is an example of caching applied to custom code?

// Check the current path is an article page.
  if ($node->getType() == 'article') {
    $variables['related_articles'] = NULL;
    // Grab the fields values.
    if ($node &&
        $node->hasField('field_tags')
      ) {
      $field_tags = $node->get('field_tags')->getValue();
      // Extract the term target_id or term name if the boolean is set to TRUE.
      $tags = bales_build_arguments($field_tags, FALSE);
      // Construct the lists using an OR statement plus sign (+).
      // If an AND statement is required, use comma (,).
      $tags_list = bales_build_field_imploded($tags);
      // The contextual filter separator is a slash / .
      $arguments = [$tags_list];
      if (count($arguments) > 0) {
        $view = Views::getView('related');
        $view->storage->invalidateCaches();
        if (is_object($view)) {
          $display = 'block_1';
          $variables['related_articles'] = load_view_block_content($view, $display, $arguments, TRUE);
        }
      }
    }
  }

I won't dive too much in to what the above actually is doing.  Except, as a summary – a block is being rendered from a view that is using contextual filter.  A key line in this code is 

$view->storage->invalidateCaches();

 

What are invalidate caches?

If invalidate caches aren't applied then a dynamic page cache will keep returning the old values.  Whereas, if changes are being made a method to clean this data out is required.  Diving a little further, if we look at the function being called we can gather a little insight as to what is happening.

public function invalidateCaches() {
  // Invalidate cache tags for cached rows.
  $tags = $this
    ->getCacheTags();
  \Drupal::service('cache_tags.invalidator')
    ->invalidateTags($tags);
}

Reviewing the function draws our attention to the cache tags request.

 

What is a cache tag?

Very simplistically it is a string.  Cache tags provide a declarative way to track which cache items depend on some data that is being managed through Drupal.  Therefore, you are able to declare many cache tags for a single render array.  Cache tags are of the form thing:identifier.  The only rule is that it cannot contain spaces.  So examples of cache tags:

  • node:18 — cache tag for Node entity with a nid value of 18
  • user:41 — cache tag for User entity with a nid value of 41
  • node_list — list cache tag for Node entities (invalidated whenever any Node entity is updated, deleted or created)
  • node_list:article — list cache tag for the article bundle (content type). Applicable to any entity + bundle type in following format: {entity_type}_list:{bundle}.

Note, any of the above examples become invalidated whenever there is a change to the data.

 

Custom cache tags

How can you define a custom cache tag?  Well an entity term reference field for lists that have a certain term, invalidation for such tags can be put in custom presave/delete entity hooks:

private function yourmodule_node_presave(NodeInterface $node) {
  $tags = [];
  if ($node->hasField('field_section')) {
    foreach ($node->get('field_section') as $item) {
      $tags[] = 'mysite:node:section:' . $item->target_id;
    }
  }
  if ($tags) {
    Cache::invalidateTags($tags);
  }
}

 

Looking at another code example

  /**
   * {@inheritdoc}
   */
  public function getCacheContexts() {
    $contexts = parent::getCacheContexts();
    $contexts[] = 'url.path';

    return $contexts;
  }

In the code above, cache contexts is being referenced.  

A cache context is a string that refers to one of the available cache context services.  Cache contexts provide a declarative way to create context-dependent variations of 'something' that has a requirement to be cached.  The sheer fact of making it declarative, code that creates caches becomes easier to read and the same logic doesn't need to be repeated in everywhere.

 

getCacheMaxAge()

getCacheMaxAge(): This method used if you want to change block cache max time.  A cache max-age is a positive integer, expressing a number of seconds.  Therefore, 100 means cacheable for 100 seconds.  Whereas, 0 means cacheable for zero seconds or not cacheable.  Otherwise, permanent means cacheable forever and is written as Cache::PERMANENT.  More details about Drupal cache is found on Drupal cache api documentation cache-max-age page.

/**
 * {@inheritdoc}
 * return 0 If you want to disable caching for this block.
 */
public function getCacheMaxAge()
{
    // return Cache::PERMANENT;
    return 0;
}

 

Related articles