public RenderCache::set(array &$elements, array $pre_bubbling_elements)
Caches the rendered output of a renderable array.
May be called by an implementation of \Drupal\Core\Render\RendererInterface while rendering, if the #cache property is set.
Parameters
array $elements: A renderable array.
array $pre_bubbling_elements: A renderable array corresponding to the state (in particular, the cacheability metadata) of $elements prior to the beginning of its rendering process, and therefore before any bubbling of child information has taken place. Only the #cache property is used by this function, so the caller may omit all other properties and children from this array.
Return value
bool|null Returns FALSE if no cache item could be created, NULL otherwise.
Overrides RenderCacheInterface::set
See also
::get()
File
- core/lib/Drupal/Core/Render/RenderCache.php, line 88
Class
- RenderCache
- Wraps the caching logic for the render caching system.
Namespace
Drupal\Core\Render
Code
public function set(array &$elements, array $pre_bubbling_elements) { // Form submissions rely on the form being built during the POST request, // and render caching of forms prevents this from happening. // @todo remove the isMethodSafe() check when // https://www.drupal.org/node/2367555 lands. if (!$this->requestStack->getCurrentRequest()->isMethodSafe() || !$cid = $this->createCacheID($elements)) { return FALSE; } $data = $this->getCacheableRenderArray($elements); $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'render'; $cache = $this->cacheFactory->get($bin); // Calculate the pre-bubbling CID. $pre_bubbling_cid = $this->createCacheID($pre_bubbling_elements); // Two-tier caching: detect different CID post-bubbling, create redirect, // update redirect if different set of cache contexts. // @see \Drupal\Core\Render\RendererInterface::render() // @see ::get() if ($pre_bubbling_cid && $pre_bubbling_cid !== $cid) { // The cache redirection strategy we're implementing here is pretty // simple in concept. Suppose we have the following render structure: // - A (pre-bubbling, specifies #cache['keys'] = ['foo']) // -- B (specifies #cache['contexts'] = ['b']) // // At the time that we're evaluating whether A's rendering can be // retrieved from cache, we won't know the contexts required by its // children (the children might not even be built yet), so cacheGet() // will only be able to get what is cached for a $cid of 'foo'. But at // the time we're writing to that cache, we do know all the contexts that // were specified by all children, so what we need is a way to // persist that information between the cache write and the next cache // read. So, what we can do is store the following into 'foo': // [ // '#cache_redirect' => TRUE, // '#cache' => [ // ... // 'contexts' => ['b'], // ], // ] // // This efficiently lets cacheGet() redirect to a $cid that includes all // of the required contexts. The strategy is on-demand: in the case where // there aren't any additional contexts required by children that aren't // already included in the parent's pre-bubbled #cache information, no // cache redirection is needed. // // When implementing this redirection strategy, special care is needed to // resolve potential cache ping-pong problems. For example, consider the // following render structure: // - A (pre-bubbling, specifies #cache['keys'] = ['foo']) // -- B (pre-bubbling, specifies #cache['contexts'] = ['b']) // --- C (pre-bubbling, specifies #cache['contexts'] = ['c']) // --- D (pre-bubbling, specifies #cache['contexts'] = ['d']) // // Additionally, suppose that: // - C only exists for a 'b' context value of 'b1' // - D only exists for a 'b' context value of 'b2' // This is an acceptable variation, since B specifies that its contents // vary on context 'b'. // // A naive implementation of cache redirection would result in the // following: // - When a request is processed where context 'b' = 'b1', what would be // cached for a $pre_bubbling_cid of 'foo' is: // [ // '#cache_redirect' => TRUE, // '#cache' => [ // ... // 'contexts' => ['b', 'c'], // ], // ] // - When a request is processed where context 'b' = 'b2', we would // retrieve the above from cache, but when following that redirection, // get a cache miss, since we're processing a 'b' context value that // has not yet been cached. Given the cache miss, we would continue // with rendering the structure, perform the required context bubbling // and then overwrite the above item with: // [ // '#cache_redirect' => TRUE, // '#cache' => [ // ... // 'contexts' => ['b', 'd'], // ], // ] // - Now, if a request comes in where context 'b' = 'b1' again, the above // would redirect to a cache key that doesn't exist, since we have not // yet cached an item that includes 'b'='b1' and something for 'd'. So // we would process this request as a cache miss, at the end of which, // we would overwrite the above item back to: // [ // '#cache_redirect' => TRUE, // '#cache' => [ // ... // 'contexts' => ['b', 'c'], // ], // ] // - The above would always result in accurate renderings, but would // result in poor performance as we keep processing requests as cache // misses even though the target of the redirection is cached, and // it's only the redirection element itself that is creating the // ping-pong problem. // // A way to resolve the ping-pong problem is to eventually reach a cache // state where the redirection element includes all of the contexts used // throughout all requests: // [ // '#cache_redirect' => TRUE, // '#cache' => [ // ... // 'contexts' => ['b', 'c', 'd'], // ], // ] // // We can't reach that state right away, since we don't know what the // result of future requests will be, but we can incrementally move // towards that state by progressively merging the 'contexts' value // across requests. That's the strategy employed below and tested in // \Drupal\Tests\Core\Render\RendererBubblingTest::testConditionalCacheContextBubblingSelfHealing(). // Get the cacheability of this element according to the current (stored) // redirecting cache item, if any. $redirect_cacheability = new CacheableMetadata(); if ($stored_cache_redirect = $cache->get($pre_bubbling_cid)) { $redirect_cacheability = CacheableMetadata::createFromRenderArray($stored_cache_redirect->data); } // Calculate the union of the cacheability for this request and the // current (stored) redirecting cache item. We need: // - the union of cache contexts, because that is how we know which cache // item to redirect to; // - the union of cache tags, because that is how we know when the cache // redirect cache item itself is invalidated; // - the union of max ages, because that is how we know when the cache // redirect cache item itself becomes stale. (Without this, we might end // up toggling between a permanently and a briefly cacheable cache // redirect, because the last update's max-age would always "win".) $redirect_cacheability_updated = CacheableMetadata::createFromRenderArray($data)->merge($redirect_cacheability); // Stored cache contexts incomplete: this request causes cache contexts to // be added to the redirecting cache item. if (array_diff($redirect_cacheability_updated->getCacheContexts(), $redirect_cacheability->getCacheContexts())) { $redirect_data = [ '#cache_redirect' => TRUE, '#cache' => [ // The cache keys of the current element; this remains the same // across requests. 'keys' => $elements['#cache']['keys'], // The union of the current element's and stored cache contexts. 'contexts' => $redirect_cacheability_updated->getCacheContexts(), // The union of the current element's and stored cache tags. 'tags' => $redirect_cacheability_updated->getCacheTags(), // The union of the current element's and stored cache max-ages. 'max-age' => $redirect_cacheability_updated->getCacheMaxAge(), // The same cache bin as the one for the actual render cache items. 'bin' => $bin, ], ]; $cache->set($pre_bubbling_cid, $redirect_data, $this->maxAgeToExpire($redirect_cacheability_updated->getCacheMaxAge()), Cache::mergeTags($redirect_data['#cache']['tags'], ['rendered'])); } // Current cache contexts incomplete: this request only uses a subset of // the cache contexts stored in the redirecting cache item. Vary by these // additional (conditional) cache contexts as well, otherwise the // redirecting cache item would be pointing to a cache item that can never // exist. if (array_diff($redirect_cacheability_updated->getCacheContexts(), $data['#cache']['contexts'])) { // Recalculate the cache ID. $recalculated_cid_pseudo_element = [ '#cache' => [ 'keys' => $elements['#cache']['keys'], 'contexts' => $redirect_cacheability_updated->getCacheContexts(), ] ]; $cid = $this->createCacheID($recalculated_cid_pseudo_element); // Ensure the about-to-be-cached data uses the merged cache contexts. $data['#cache']['contexts'] = $redirect_cacheability_updated->getCacheContexts(); } } $cache->set($cid, $data, $this->maxAgeToExpire($elements['#cache']['max-age']), Cache::mergeTags($data['#cache']['tags'], ['rendered'])); }
Please login to continue.