function CacheContextsManager::optimizeTokens

Same name and namespace in other branches
  1. 11.x core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php \Drupal\Core\Cache\Context\CacheContextsManager::optimizeTokens()
  2. 10 core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php \Drupal\Core\Cache\Context\CacheContextsManager::optimizeTokens()
  3. 9 core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php \Drupal\Core\Cache\Context\CacheContextsManager::optimizeTokens()
  4. 8.9.x core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php \Drupal\Core\Cache\Context\CacheContextsManager::optimizeTokens()

Optimizes cache context tokens (the minimal representative subset).

A minimal representative subset means that any cache context token in the given set of cache context tokens that is a property of another cache context cache context token in the set, is removed.

Hence a minimal representative subset is the most compact representation possible of a set of cache context tokens, that still captures the entire universe of variations.

If a cache context is being optimized away, it is able to set cacheable metadata for itself which will be bubbled up.

For example, when caching per user ('user'), also caching per role ('user.roles') is meaningless because "per role" is implied by "per user".

In the following examples, remember that the period indicates hierarchy and the colon can be used to get a specific value of a calculated cache context:

  • ['a', 'a.b'] -> ['a']
  • ['a', 'a.b.c'] -> ['a']
  • ['a.b', 'a.b.c'] -> ['a.b']
  • ['a', 'a.b', 'a.b.c'] -> ['a']
  • ['x', 'x:foo'] -> ['x']
  • ['a', 'a.b.c:bar'] -> ['a']

Parameters

string[] $context_tokens: A set of cache context tokens.

Return value

string[] A representative subset of the given set of cache context tokens.

1 call to CacheContextsManager::optimizeTokens()
CacheContextsManager::convertTokensToKeys in core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php
Converts cache context tokens to cache keys.

File

core/lib/Drupal/Core/Cache/Context/CacheContextsManager.php, line 164

Class

CacheContextsManager
Converts cache context tokens into cache keys.

Namespace

Drupal\Core\Cache\Context

Code

public function optimizeTokens(array $context_tokens) {
  // A single token (or empty array) cannot be optimized.
  if (count($context_tokens) <= 1) {
    return $context_tokens;
  }
  // Use hash table for O(1) ancestor lookups instead of O(n) in_array().
  $context_tokens_lookup = array_flip($context_tokens);
  $optimized_content_tokens = [];
  foreach ($context_tokens as $context_token) {
    $has_period = str_contains($context_token, '.');
    $has_colon = str_contains($context_token, ':');
    // Context tokens without a period or colon have no parent,
    // hence no optimizations are possible.
    if (!$has_period && !$has_colon) {
      $optimized_content_tokens[] = $context_token;
      continue;
    }
    // Check for ancestors first (cheap string operations) before calling
    // getService() which is more expensive.
    $ancestor_found = FALSE;
    // Treat a colon like a period, that allows us to consider 'a' the
    // ancestor of 'a:foo', without any additional code for the colon.
    $ancestor = $has_colon ? str_replace(':', '.', $context_token) : $context_token;
    do {
      $ancestor = substr($ancestor, 0, strrpos($ancestor, '.'));
      if (isset($context_tokens_lookup[$ancestor])) {
        // An ancestor cache context is in $context_tokens, hence this cache
        // context is implied.
        $ancestor_found = TRUE;
        break;

      }
    } while (str_contains($ancestor, '.'));
    if ($ancestor_found) {
      // Ancestor found. Check if this token can be optimized away by
      // verifying its max-age is not 0.
      // Note: We intentionally do NOT cache getCacheableMetadata() results
      // because the metadata can change during a request (e.g., when user
      // permissions or roles change). Caching could lead to incorrect
      // optimization decisions.
      $parameter = NULL;
      $context_id = $context_token;
      if ($has_colon) {
        [$context_id, $parameter] = explode(':', $context_token, 2);
      }
      // If max-age is 0, the token cannot be optimized away.
      $max_age = $this->getService($context_id)
        ->getCacheableMetadata($parameter)
        ->getCacheMaxAge();
      if ($max_age === 0) {
        $optimized_content_tokens[] = $context_token;
      }
    }
    else {
      // No ancestor exists, keep this token.
      $optimized_content_tokens[] = $context_token;
    }
  }
  return $optimized_content_tokens;
}

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.