CacheImg
2019 29 Jul

How does entity cache work in Drupal 8

The Drupal Cache API is used to store data that takes a long time to compute. Caching can either be permanent or valid only for a certain time span, and the cache can contain any type of data. To make websites faster Drupal stores web pages in a cache.

Drupal Cache has three properties

  • Cache context creates context variations when render arrays are being generated. If we have user as a context, every user may not have the same user permissions or language.
  • Cache tags define what object the cache depends on. For dependencies on data managed by Drupal, like entities and configuration.
  • Cache max-age is the maximum time that the cache is stored.

Here is an example from a custom block in Drupal 8:

use Drupal\Core\Cache\Cache;

return [
      '#theme' => 'user_profile_template',
      '#user_data' => $user_data,
      '#cache' => [
        'tags' => ['languages', 'timezone'],
        'contexts' => ['node:5', 'user:3'],
        'max-age' => Cache::PERMANENT,
      ],
];

Use of cache tags in Entity Caching

The cached data in different bins becomes old and obsolete at some point of time and requires removal from these bins to accommodate the latest changes. Before Drupal 8, there was no way to identify individual pieces of expired data stored in different cache bins. 

Cache tags provides a way to track which cache items depend on some data managed by Drupal.
If a renderable output which is output of a Controller or a custom block depends on content provided by some entity, we use cache tags to invalidate the data.
For example, if a node is updated, which appear in two views and three blocks. Without cache tags we wouldn't know which cache item to invalidate

The syntax for setting cache tags is thing:identifier. It has to be unique string and cannot contain spaces.

Entities gets caches in the form of <entity type ID>:<entity ID>

'tags' => ['node_list'], //invalidate when any node updates
'tags' => ['node:1','term:2'], //invalidate when node id 1 or term id 2 is updated
 

We can also define our own cache tag:

  • Request a cache object through \Drupal::cache().
  • Define a Cache ID (cid) value for your data. A cid is a string, which must contain enough information to uniquely identify the data.
  • Call the get() method to attempt a cache read, to see if the cache already contains your data.
  • If your data is not already in the cache, compute it and add it to the cache using the set() method.
$nid = 9;
$cid = 'my_module:' . $nid;

// Check if the cache already contain data.
if ($item = \Drupal::cache()->get($cid)) {
  return $item->data;
}

// The data to be cached.
$node = Node::load($nid);
$data = [
  'title' => sprintf('## %s', $node->get('title')->getValue()),
  //...
];

// Set the cache for 'my_module:' . $nid cache tag until $node changes.
\Drupal::cache()->set($cid, $data, Cache::PERMANENT, $node->getCacheTags());

A cache item can have multiple cache tags (an array of cache tags), and each cache tag is a string. Drupal associated cache tags with entity and entity listings. It is important to invalidate listings-based caches when an entity no longer exists or when a new entity is created. This can be done using EntityTypeInterface::getListCacheTags(), it enables code listing entities of this type to ensure that newly created entities show up immediately or invalidate the ones that don’t exist.

Cache::invalidateTags is used to invalidate all cached data of a certain cache tag.

// Invalidate all cache items with certain tags.
\Drupal\Core\Cache\Cache::invalidateTags(array('node:1',  'user:7'));

Explaining with an Example

Create a file custom_plugin/custom_plugin.services.yml in your custom module.

services:
  custom_plugin.my_cache:
    class: Drupal\Core\Cache\CacheBackendInterface
    tags:
      - { name: cache.bin }
    factory: cache_factory:get
    arguments: [my_cache]

This is to declare our cache whose identifier will be my_cache.

Create a routing for your controller custom_plugin/custom_plugin.routing.yml

custom_plugin.cache:
  path: '/my-cache'
  defaults:
    _controller: 'Drupal\custom_plugin\Controller\CacheController::content'
    _title: 'Cache'
  requirements:
    _permission: 'access content'

Then in our Controller custom_plugin/src/Controller/CacheController.php we will create a custom cache tag to store a dynamic cache item with conditions to invalidate when the value is updated.

<?php

namespace Drupal\custom_plugin\Controller;

use Drupal\user\Entity\User;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Cache\Cache;

/**
 * Class CacheController.
 */
class CacheController extends ControllerBase {

  /**
   * The cache backend service.
   *
   * @var \Drupal\Core\Cache\CacheBackendInterface
   */
  protected $cacheBackend;

  /**
   * Constructs a new CacheController object.
   */
  public function __construct(CacheBackendInterface $cache_backend) {
    $this->cacheBackend = $cache_backend;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('custom_plugin.my_cache')
    );
  }

  /**
   * Build the user dynamic data.
   *
   * @return array
   *   Return the render array of the user dynamic data.
   */
  public function content() {
    $user = User::load(\Drupal::currentUser()->id());

    // Create a custom cache tag.
    $cid = 'custom_plugin:' . $user->id();
    // Check if there is any cache item associated with this cache tag.
    $data_cached = $this->cacheBackend->get($cid);

    if (!$data_cached) {
      // Build the user dynamic data.
      $data = $user->getAccountName() . ' last accessed at ' . date('H:i', $user->getLastAccessedTime());

      // Merge the entity cache of an user entity with our custom tag.
      $tags = Cache::mergeTags(['user:' . $user->id()], [$cid]);

      // // Store the data into the cache.
      $this->cacheBackend->set($cid, $data, CacheBackendInterface::CACHE_PERMANENT, $tags);
    }
    else {
      $data = $data_cached->data;
      $tags = $data_cached->tags;
    }

    // Return a renderable output.
    $build = [
      '#theme' => 'user_data',
      '#user' => $user->id(),
      '#data' => $data,
      '#cache' => [
        'tags' => $tags,
        'context' => ['user'],
      ],
    ];

    return $build;
  }

}

Initialize the variables to be used in the template in custom_plugin/custom_plugin.module file.

/**
 * Implements hook_theme().
 */
function custom_plugin_theme() {
  return [
    'user_data' => [
      'variables' => [
        'user' => [],
        'data' => [],
      ],
    ],
  ];
}

Create a template custom_plugin/templates/user-data.html.twig to print the cache items

<div class="custom-plugin-block">
  <p>User ID: {{ user }}</p>
  <p>{{ data }}</p>
</div>

Most developers and development teams have one cache invalidation strategy i.e. clear all cache. And that is not a good idea for complex websites and applications which have a huge amount of content. This custom cache invalidation strategy will help you clear only the required cache and keep the rest intact. This can boost the performance a lot and goes without saying, you have a better control of your site's cache.

Featured blog

web-personalization

Personalized Content is a Proven Entity !!

Irrespective of how big a business icon or brand you are, increasing the relevance of your website will always be critical to your success.

Read More

Git Hooks

Git hooks for better codes

We are programmers and we are always on the lookout for ways to improve our code. A good and structured way of coding defines the completeness of a programmer.

Read More

Drupal ,varnish cache

Hard time with Drupal, Varnish Cache and Cookies

Using a reverse proxy server in front of a web server is usually needed for every big site and it is a very good thing to do so as reverse proxy server will handle all the anonymous traff

Read More

Say no to captcha

Say no to captcha - Various Spam Protection Methods

Maintaining high traffic websites have their own merits and demerits, the most annoying thing about them is SPAM.

Read More