function VariationCache::set

Same name in other branches
  1. 11.x core/lib/Drupal/Core/Cache/VariationCache.php \Drupal\Core\Cache\VariationCache::set()

Overrides VariationCacheInterface::set

File

core/lib/Drupal/Core/Cache/VariationCache.php, line 42

Class

VariationCache
Wraps a regular cache backend to make it support cache contexts.

Namespace

Drupal\Core\Cache

Code

public function set(array $keys, $data, CacheableDependencyInterface $cacheability, CacheableDependencyInterface $initial_cacheability) : void {
    $initial_contexts = $initial_cacheability->getCacheContexts();
    $contexts = $cacheability->getCacheContexts();
    if ($missing_contexts = array_diff($initial_contexts, $contexts)) {
        throw new \LogicException(sprintf('The complete set of cache contexts for a variation cache item must contain all of the initial cache contexts, missing: %s.', implode(', ', $missing_contexts)));
    }
    // Don't store uncacheable items.
    if ($cacheability->getCacheMaxAge() === 0) {
        return;
    }
    // Track the potential effect of cache context optimization on cache tags.
    $optimized_cacheability = CacheableMetadata::createFromObject($cacheability);
    $cid = $this->createCacheId($keys, $optimized_cacheability);
    // Check whether we had any cache redirects leading to the cache ID already.
    // If there are none, we know that there is no proper redirect path to the
    // cache ID we're trying to store the data at. This may be because there is
    // either no full redirect path yet or there is one that is too specific at
    // a given step of the way. In case of the former, we simply need to store a
    // redirect. In case of the latter, we need to replace the overly specific
    // step with a simpler one.
    $chain = $this->getRedirectChain($keys, $initial_cacheability);
    if (!array_key_exists($cid, $chain)) {
        // We can easily find overly specific redirects by comparing their cache
        // contexts to the ones we have here. If a redirect has more or different
        // contexts, it needs to be replaced with a simplified version.
        //
        // Simplifying overly specific redirects can be done in two ways:
        //
        // -------
        //
        // Problem: The redirect is a superset of the current cache contexts.
        // Solution: We replace the redirect with the current contexts.
        //
        // Example: Suppose we try to store an object with context A, whereas we
        // already have a redirect that uses A and B. In this case we simply store
        // the object at the address designated by context A and next time someone
        // tries to load the initial AB object, it will restore its redirect path
        // by adding an AB redirect step after A.
        //
        // -------
        //
        // Problem: The redirect overlaps, with both options having unique values.
        // Solution: Find the common contexts and use those for a new redirect.
        //
        // Example: Suppose we try to store an object with contexts A and C, but
        // we already have a redirect that uses A and B. In this case we find A to
        // be the common cache context and replace the redirect with one only
        // using A, immediately followed by one for AC so there is a full path to
        // the data we're trying to set. Next time someone tries to load the
        // initial AB object, it will restore its redirect path by adding an AB
        // redirect step after A.
        foreach ($chain as $chain_cid => $result) {
            if ($result && $result->data instanceof CacheRedirect) {
                $result_contexts = $result->data
                    ->getCacheContexts();
                if (array_diff($result_contexts, $contexts)) {
                    // Check whether we have an overlap scenario as we need to manually
                    // create an extra redirect in that case.
                    $common_contexts = array_intersect($result_contexts, $contexts);
                    // != is the most appropriate comparison operator here, since we
                    // only want to know if any keys or values don't match.
                    if ($common_contexts != $contexts) {
                        // Set the redirect to the common contexts at the current address.
                        // In the above example this is essentially overwriting the
                        // redirect to AB with a redirect to A.
                        $common_cacheability = (new CacheableMetadata())->setCacheContexts($common_contexts);
                        $this->cacheBackend
                            ->set($chain_cid, new CacheRedirect($common_cacheability));
                        // Before breaking the loop, set the current address to the next
                        // one in line so that we can store the full redirect as well. In
                        // the above example, this is the part where we immediately also
                        // store a redirect to AC at the CID that A pointed to.
                        $chain_cid = $this->createCacheIdFast($keys, $common_cacheability);
                    }
                    break;
                }
            }
        }
        // The loop above either broke at an overly specific step or completed
        // without any problem. In both cases, $chain_cid ended up with the value
        // that we should store the new redirect at.
        //
        // Cache redirects are stored indefinitely and without tags as they never
        // need to be cleared. If they ever end up leading to a stale cache item
        // that now uses different contexts then said item will either follow an
        // existing path of redirects or carve its own over the old one.
        
        /** @phpstan-ignore-next-line */
        $this->cacheBackend
            ->set($chain_cid, new CacheRedirect($cacheability));
    }
    $this->cacheBackend
        ->set($cid, $data, $this->maxAgeToExpire($cacheability->getCacheMaxAge()), $optimized_cacheability->getCacheTags());
}

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