CacheContextsManager.php in Drupal 10
Same filename and directory in other branches
Namespace
Drupal\Core\Cache\ContextFile
core/lib/Drupal/Core/Cache/Context/CacheContextsManager.phpView source
<?php
namespace Drupal\Core\Cache\Context;
use Drupal\Core\Cache\CacheableMetadata;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Converts cache context tokens into cache keys.
*
* Uses cache context services (services tagged with 'cache.context', and whose
* service ID has the 'cache_context.' prefix) to dynamically generate cache
* keys based on the request context, thus allowing developers to express the
* state by which should varied (the current URL, language, and so on).
*
* Note that this maps exactly to HTTP's Vary header semantics:
* @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.44
*
* @see \Drupal\Core\Cache\Context\CacheContextInterface
* @see \Drupal\Core\Cache\Context\CalculatedCacheContextInterface
* @see \Drupal\Core\Cache\Context\CacheContextsPass
*/
class CacheContextsManager {
/**
* The service container.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
/**
* Available cache context IDs and corresponding labels.
*
* @var string[]
*/
protected $contexts;
/**
* Constructs a CacheContextsManager object.
*
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* The current service container.
* @param string[] $contexts
* An array of the available cache context IDs.
*/
public function __construct(ContainerInterface $container, array $contexts) {
$this->container = $container;
$this->contexts = $contexts;
}
/**
* Provides an array of available cache contexts.
*
* @return string[]
* An array of available cache context IDs.
*/
public function getAll() {
return $this->contexts;
}
/**
* Provides an array of available cache context labels.
*
* To be used in cache configuration forms.
*
* @param bool $include_calculated_cache_contexts
* Whether to also return calculated cache contexts. Default to FALSE.
*
* @return array
* An array of available cache contexts and corresponding labels.
*/
public function getLabels($include_calculated_cache_contexts = FALSE) {
$with_labels = [];
foreach ($this->contexts as $context) {
$service = $this
->getService($context);
if (!$include_calculated_cache_contexts && $service instanceof CalculatedCacheContextInterface) {
continue;
}
$with_labels[$context] = $service
->getLabel();
}
return $with_labels;
}
/**
* Converts cache context tokens to cache keys.
*
* A cache context token is either:
* - a cache context ID (if the service ID is 'cache_context.foo', then 'foo'
* is a cache context ID); for example, 'foo'.
* - a calculated cache context ID, followed by a colon, followed by
* the parameter for the calculated cache context; for example,
* 'bar:some_parameter'.
*
* @param string[] $context_tokens
* An array of cache context tokens.
*
* @return \Drupal\Core\Cache\Context\ContextCacheKeys
* The ContextCacheKeys object containing the converted cache keys and
* cacheability metadata.
*/
public function convertTokensToKeys(array $context_tokens) {
assert($this
->assertValidTokens($context_tokens));
$cacheable_metadata = new CacheableMetadata();
$optimized_tokens = $this
->optimizeTokens($context_tokens);
// Iterate over cache contexts that have been optimized away and get their
// cacheability metadata.
foreach (static::parseTokens(array_diff($context_tokens, $optimized_tokens)) as $context_token) {
[
$context_id,
$parameter,
] = $context_token;
$context = $this
->getService($context_id);
$cacheable_metadata = $cacheable_metadata
->merge($context
->getCacheableMetadata($parameter));
}
sort($optimized_tokens);
$keys = [];
foreach (array_combine($optimized_tokens, static::parseTokens($optimized_tokens)) as $context_token => $context) {
[
$context_id,
$parameter,
] = $context;
$keys[] = '[' . $context_token . ']=' . $this
->getService($context_id)
->getContext($parameter);
}
// Create the returned object and merge in the cacheability metadata.
$context_cache_keys = new ContextCacheKeys($keys);
return $context_cache_keys
->merge($cacheable_metadata);
}
/**
* 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']
*
* @param string[] $context_tokens
* A set of cache context tokens.
*
* @return string[]
* A representative subset of the given set of cache context tokens..
*/
public function optimizeTokens(array $context_tokens) {
$optimized_content_tokens = [];
foreach ($context_tokens as $context_token) {
// Extract the parameter if available.
$parameter = NULL;
$context_id = $context_token;
if (strpos($context_token, ':') !== FALSE) {
[
$context_id,
$parameter,
] = explode(':', $context_token);
}
// Context tokens without:
// - a period means they don't have a parent
// - a colon means they're not a specific value of a cache context
// hence no optimizations are possible.
if (strpos($context_token, '.') === FALSE && strpos($context_token, ':') === FALSE) {
$optimized_content_tokens[] = $context_token;
}
elseif ($this
->getService($context_id)
->getCacheableMetadata($parameter)
->getCacheMaxAge() === 0) {
$optimized_content_tokens[] = $context_token;
}
else {
$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 = str_replace(':', '.', $context_token);
do {
$ancestor = substr($ancestor, 0, strrpos($ancestor, '.'));
if (in_array($ancestor, $context_tokens)) {
// An ancestor cache context is in $context_tokens, hence this cache
// context is implied.
$ancestor_found = TRUE;
}
} while (!$ancestor_found && strpos($ancestor, '.') !== FALSE);
if (!$ancestor_found) {
$optimized_content_tokens[] = $context_token;
}
}
}
return $optimized_content_tokens;
}
/**
* Retrieves a cache context service from the container.
*
* @param string $context_id
* The context ID, which together with the service ID prefix allows the
* corresponding cache context service to be retrieved.
*
* @return \Drupal\Core\Cache\Context\CacheContextInterface
* The requested cache context service.
*/
protected function getService($context_id) {
return $this->container
->get('cache_context.' . $context_id);
}
/**
* Parses cache context tokens into context IDs and optional parameters.
*
* @param string[] $context_tokens
* An array of cache context tokens.
*
* @return array
* An array with the parsed results, with each result being an array
* containing:
* - The cache context ID.
* - The associated parameter (for a calculated cache context), or NULL if
* there is no parameter.
*/
public static function parseTokens(array $context_tokens) {
$contexts_with_parameters = [];
foreach ($context_tokens as $context) {
$context_id = $context;
$parameter = NULL;
if (strpos($context, ':') !== FALSE) {
[
$context_id,
$parameter,
] = explode(':', $context, 2);
}
$contexts_with_parameters[] = [
$context_id,
$parameter,
];
}
return $contexts_with_parameters;
}
/**
* Validates an array of cache context tokens.
*
* Can be called before using cache contexts in operations, to check validity.
*
* @param string[] $context_tokens
* An array of cache context tokens.
*
* @throws \LogicException
*
* @see \Drupal\Core\Cache\Context\CacheContextsManager::parseTokens()
*/
public function validateTokens(array $context_tokens = []) {
if (empty($context_tokens)) {
return;
}
// Initialize the set of valid context tokens with the container's contexts.
if (!isset($this->validContextTokens)) {
$this->validContextTokens = array_flip($this->contexts);
}
foreach ($context_tokens as $context_token) {
if (!is_string($context_token)) {
throw new \LogicException(sprintf('Cache contexts must be strings, %s given.', gettype($context_token)));
}
if (isset($this->validContextTokens[$context_token])) {
continue;
}
// If it's a valid context token, then the ID must be stored in the set
// of valid context tokens (since we initialized it with the list of cache
// context IDs using the container). In case of an invalid context token,
// throw an exception, otherwise cache it, including the parameter, to
// minimize the amount of work in future ::validateContexts() calls.
$context_id = $context_token;
$colon_pos = strpos($context_id, ':');
if ($colon_pos !== FALSE) {
$context_id = substr($context_id, 0, $colon_pos);
}
if (isset($this->validContextTokens[$context_id])) {
$this->validContextTokens[$context_token] = TRUE;
}
else {
throw new \LogicException(sprintf('"%s" is not a valid cache context ID.', $context_id));
}
}
}
/**
* Asserts the context tokens are valid.
*
* Similar to ::validateTokens, this method returns boolean TRUE when the
* context tokens are valid, and FALSE when they are not instead of returning
* NULL when they are valid and throwing a \LogicException when they are not.
* This function should be used with the assert() statement.
*
* @param mixed $context_tokens
* Variable to be examined - should be array of context_tokens.
*
* @return bool
* TRUE if context_tokens is an array of valid tokens.
*/
public function assertValidTokens($context_tokens) {
if (!is_array($context_tokens)) {
return FALSE;
}
try {
$this
->validateTokens($context_tokens);
} catch (\LogicException $e) {
return FALSE;
}
return TRUE;
}
}
Classes
Name | Description |
---|---|
CacheContextsManager | Converts cache context tokens into cache keys. |