cache_context
2019 30 Jul

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

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 https://www.innoraft.com/blogs/how-does-entity-cache-work-drupal-8. 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 at https://www.drupal.org/docs/8/api/cache-api/cache-contexts .

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 at https://www.drupal.org/docs/8/api/services-and-dependency-injection/structure-of-a-service-file. 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 https://www.drupal.org/docs/8/api/responses/cacheableresponseinterface#debugging

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. 

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