Leverage Drupal 8 Cache Context for Contextual Caching Skip to main content

Search

30 Jul, 2019
4 min read

Drupal 8 Cache Context: An efficient way for context based caching.

Image
Drupal 8 Cache Context: An efficient way for context based caching - Banner

We are well aware of the fact that Drupal Cache API is a remarkable feature introduced in Drupal 8. Still, this topic remains unrevealed to many developers as they consider caching to be a critical aspect of a website. In one of our earlier posts, we have exemplified Cache tags. Here is a guide that helps you easily grab in some basic concepts of Cache Context. 

Cache Context is basically a service that helps in creating multiple cached versions of something depending upon the context/request; be it a view, block or any other section on the page. 

For instance, let us consider a block displaying a list of tutorial links on a D8 instance. Now authorised users will be given access to all the links while anonymous ones will be provided only with the free tutorials. This data completely depends upon the role of the user. Hence ‘user.roles’ can be used as a cache context in such a scenario. For simplicity let us assume that there exist only two roles authenticated and anonymous. When an authenticated user hits the page, the version of the block with access to all links will be displayed. Hereafter if another authenticated user visits the page; the cached version of the block will be served thereby enhancing the site performance. When an anonymous user comes to the same page the entire request is carried out and the display with limited access to links will be shown. In such a way we can explicitly decide as to when the cache of the element will be invalidated based on the context.

D8 core provides few predefined cache contexts that are available here.

Our main focus here would be how to define and use a custom cache_context according to our requirement.

Let us consider a simple example to invalidate the cache of a block that displays a personalised message based on the summary that the user has provided in the user edit page.

Prerequisite: Add a field for filling in summary in the user edit form([base_url]/user/[user_id]/edit) provided by default in drupal.

Cache context can be registered as any other service in the module.services.yml file:

services:
  cache_context.user_summary:
    class: Drupal\example_cache_context\CacheContext\UserSummaryCacheContext
    arguments: ['@current_user']
    tags:
      - { name: cache_context }

   example_cache_context.services.yml

The trick here is to understand the naming convention for a new cache context. The name of the service should be of the format cache_context.* i.e should start with ‘cache_context.’ followed by the appropriate name. Hence the name cache_context.user_summary. Similarly, we can define a further level of hierarchy as well. As per the above snippet, the code for cache context goes into src/CacheContext/UserSummaryCacheContext.php. The service takes in the current_user service as an argument. The detail on how to pass a service as an argument to another is available here. We need to tag this service to cache_context as well.

The summary field should be used to create this cache_context logic as per the following code snippet:


<?php

namespace Drupal\example_cache_context\CacheContext;

use Drupal\Core\Cache\Context\CacheContextInterface;
use Drupal\Core\Session\AccountProxy;
use Drupal\user\Entity\User;
use Drupal\Core\Cache\CacheableMetadata;

class UserSummaryCacheContext implements CacheContextInterface {
  /**
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
	protected $user_current;

  /**
  * {@inheritdoc}
  */
	public function __construct(AccountProxy $user_current) {
		$this->user_current = $user_current;
	}

  /**
  * {@inheritdoc}
  */
	public static function getLabel() {
		return t('User Summary cache context');
	}

  /**
  * {@inheritdoc}
  */
	public function getContext() {
    $id = $this->user_current->id();
    $user_details = User::load($id);
    $summary = $user_details->get('field_summary')->getValue()[0]['value'];
    return $summary;
	}

  /**
  * {@inheritdoc}
  */
  public function getCacheableMetadata() {
    return new CacheableMetadata();
  }
}


UserSummaryCacheContext.php

Here the SummaryCacheContext class implements the interface CacheContextInterface. The variable $user_current which is an instance of AccountProxyInterface is declared protected and used as per the arguments mentioned in the services.yml file. The function getContext() contains necessary code for the cache invalidation based on the context. We can implement any other logic according to our requirement here.

The CacheContext code is now ready to use. In order to test the code, create a block within src/Plugin/Blocks and place it on any page:


<?php

namespace Drupal\example_cache_context\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\Cache;
use Drupal\user\Entity\User;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Provides a block for particular user's summary
 *
 * @Block(
 * id = "user_summary_block",
 * admin_label = @Translation("User Summary Block")
 * )
 */


class UserSummary extends BlockBase {

  /**
   * {@inheritdoc}
   */
	public function build() {
		$build = [];
		$UserId = \Drupal::currentUser()- >id();
		$user = User::load($UserId);
		$summary = $user->get('field_summary')->getValue()[0]['value'];
		$build['user_summary'] = [
			'#markup' => $summary
		]; 
		return $build;
	}

  /**
   * {@inheritdoc}
   */
  public function getCacheContexts() {
  	return Cache::mergeContexts(
  		parent::getCacheContexts(),
  		['user_summary']
  	);
  }
}

 UserSummary.php

To verify if the context has been added properly, use the chrome dev tools:

dev_tools

 

If these headers are not visible you might need to configure settings to enable these as per given guidelines.

This eliminates the need for cache clearance of entire site after the update of any data which slows down the website. Hope this blog gave a better insight as to the usage of cache context in Drupal 8. You can now invalidate cache as per the context keeping in mind the performance of the site while updating the user of every new piece of information appearing on the website.