Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
66.67% |
6 / 9 |
CRAP | |
77.63% |
59 / 76 |
| CacheContextsManager | |
0.00% |
0 / 1 |
|
66.67% |
6 / 9 |
46.94 | |
77.63% |
59 / 76 |
| __construct | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
| getAll | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
| getLabels | |
100.00% |
1 / 1 |
4 | |
100.00% |
7 / 7 |
|||
| convertTokensToKeys | |
0.00% |
0 / 1 |
3.09 | |
78.57% |
11 / 14 |
|||
| optimizeTokens | |
100.00% |
1 / 1 |
10 | |
100.00% |
19 / 19 |
|||
| getService | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
| parseTokens | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 8 |
|||
| validateTokens | |
100.00% |
1 / 1 |
8 | |
100.00% |
17 / 17 |
|||
| assertValidTokens | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 6 |
|||
| <?php | |
| /** | |
| * @file | |
| * Contains \Drupal\Core\Cache\Context\CacheContextsManager. | |
| */ | |
| 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 = array(); | |
| 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) { | |
| list($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) { | |
| list($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) { | |
| list($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; | |
| } | |
| // Check cacheability. If the context defines a max-age of 0, then it | |
| // can not be optimized away. Pass the parameter along if we have one. | |
| elseif ($this->getService($context_id)->getCacheableMetadata($parameter)->getCacheMaxAge() === 0) { | |
| $optimized_content_tokens[] = $context_token; | |
| } | |
| // The context token has a period or a colon. Iterate over all ancestor | |
| // cache contexts. If one exists, omit the 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) { | |
| list($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; | |
| } | |
| } |