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

Andrew Fletcher04 Apr 2025
Managing .gitignore changes
When working with Git, the .gitignore file plays a critical role in controlling which files and folders are tracked by version control. Yet, many developers are unsure when changes to .gitignore take effect and how to manage files that are already being tracked. This uncertainty can lead to...
Andrew Fletcher26 Mar 2025
How to fix the ‘Undefined function t’ error in Drupal 10 or 11 code
Upgrading to Drupal 10.4+ you might have noticed a warning in their code editor stating “Undefined function ‘t’”. While Drupal’s `t()` function remains valid in procedural code, some language analysis tools — such as Intelephense — do not automatically recognise Drupal’s global functions. This...
Andrew Fletcher17 Mar 2025
Upgrading to PHP 8.4 challenges with Drupal contrib modules
The upgrade from PHP 8.3.14 to PHP 8.4.4 presents challenges for Drupal 10.4 websites, particularly when dealing with contributed modules. While Drupal core operates seamlessly, various contrib modules have not yet been updated to accommodate changes introduced in PHP 8.4.x. This has resulted in...