PlaceholderingRenderCache.php
Same filename in other branches
Namespace
Drupal\Core\RenderFile
-
core/
lib/ Drupal/ Core/ Render/ PlaceholderingRenderCache.php
View source
<?php
namespace Drupal\Core\Render;
use Drupal\Core\Cache\CacheFactoryInterface;
use Drupal\Core\Cache\Context\CacheContextsManager;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Adds automatic placeholdering to the RenderCache.
*
* This automatic placeholdering is performed to ensure the containing elements
* and overarching response are as cacheable as possible. Elements whose subtree
* bubble either max-age=0 or high-cardinality cache contexts (such as 'user'
* and 'session') are considered poorly cacheable.
*
* @see sites/default/default.services.yml
*
* Automatic placeholdering is performed only on elements whose subtree was
* generated using a #lazy_builder callback and whose bubbled cacheability meets
* the auto-placeholdering conditions as configured in the renderer.config
* container parameter.
*
* This RenderCache implementation automatically replaces an element with a
* placeholder:
* - on render cache hit, i.e. ::get()
* - on render cache miss, i.e. ::set() (in subsequent requests, it will be a
* cache hit)
*
* In either case, the render cache is guaranteed to contain the to-be-rendered
* placeholder, so replacing (rendering) the placeholder will be very fast.
*
* Finally, in case the render cache item disappears between the time it is
* decided to automatically placeholder the element and the time where the
* placeholder is replaced (rendered), that is guaranteed to not be problematic.
* Because this only automatically placeholders elements that have a
* #lazy_builder callback set, which means that in the worst case, it will need
* to be re-rendered.
*/
class PlaceholderingRenderCache extends RenderCache {
/**
* The placeholder generator.
*
* @var \Drupal\Core\Render\PlaceholderGeneratorInterface
*/
protected $placeholderGenerator;
/**
* Stores rendered results for automatically placeholdered elements.
*
* This allows us to avoid talking to the cache twice per auto-placeholdered
* element, or in case of an uncacheable element, to render it twice.
*
* Scenario A. The double cache read would happen because:
* 1. when rendering, cache read, but auto-placeholdered
* 2. when rendering placeholders, again cache read
*
* Scenario B. The cache write plus read would happen because:
* 1. when rendering, cache write, but auto-placeholdered
* 2. when rendering placeholders, cache read
*
* Scenario C. The double rendering for an uncacheable element would happen because:
* 1. when rendering, not cacheable, but auto-placeholdered
* 2. when rendering placeholders, rendered again
*
* In all three scenarios, this static cache avoids the second step, thus
* avoiding expensive work.
*
* @var array
*/
protected $placeholderResultsCache = [];
/**
* Constructs a new PlaceholderingRenderCache object.
*
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* The request stack.
* @param \Drupal\Core\Cache\CacheFactoryInterface $cache_factory
* The cache factory.
* @param \Drupal\Core\Cache\Context\CacheContextsManager $cache_contexts_manager
* The cache contexts manager.
* @param \Drupal\Core\Render\PlaceholderGeneratorInterface $placeholder_generator
* The placeholder generator.
*/
public function __construct(RequestStack $request_stack, CacheFactoryInterface $cache_factory, CacheContextsManager $cache_contexts_manager, PlaceholderGeneratorInterface $placeholder_generator) {
parent::__construct($request_stack, $cache_factory, $cache_contexts_manager);
$this->placeholderGenerator = $placeholder_generator;
}
/**
* {@inheritdoc}
*/
public function get(array $elements) {
// @todo remove this check when https://www.drupal.org/node/2367555 lands.
if (!$this->requestStack
->getCurrentRequest()
->isMethodCacheable()) {
return FALSE;
}
// When rendering placeholders, special case auto-placeholdered elements:
// avoid retrieving them from cache again, or rendering them again.
if (isset($elements['#create_placeholder']) && $elements['#create_placeholder'] === FALSE) {
$cached_placeholder_result = $this->getFromPlaceholderResultsCache($elements);
if ($cached_placeholder_result !== FALSE) {
return $cached_placeholder_result;
}
}
$cached_element = parent::get($elements);
if ($cached_element === FALSE) {
return FALSE;
}
else {
if ($this->placeholderGenerator
->canCreatePlaceholder($elements) && $this->placeholderGenerator
->shouldAutomaticallyPlaceholder($cached_element)) {
return $this->createPlaceholderAndRemember($cached_element, $elements);
}
return $cached_element;
}
}
/**
* {@inheritdoc}
*/
public function set(array &$elements, array $pre_bubbling_elements) {
$result = parent::set($elements, $pre_bubbling_elements);
// @todo remove this check when https://www.drupal.org/node/2367555 lands.
if (!$this->requestStack
->getCurrentRequest()
->isMethodCacheable()) {
return FALSE;
}
if ($this->placeholderGenerator
->canCreatePlaceholder($pre_bubbling_elements) && $this->placeholderGenerator
->shouldAutomaticallyPlaceholder($elements)) {
// Overwrite $elements with a placeholder. The Renderer (which called this
// method) will update the context with the bubbleable metadata of the
// overwritten $elements.
$elements = $this->createPlaceholderAndRemember($this->getCacheableRenderArray($elements), $pre_bubbling_elements);
}
return $result;
}
/**
* Create a placeholder for a renderable array and remember in a static cache.
*
* @param array $rendered_elements
* A fully rendered renderable array.
* @param array $pre_bubbling_elements
* A renderable array corresponding to the state (in particular, the
* cacheability metadata) of $rendered_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 array
* Renderable array with placeholder markup and the attached placeholder
* replacement metadata.
*/
protected function createPlaceholderAndRemember(array $rendered_elements, array $pre_bubbling_elements) {
$placeholder_element = $this->placeholderGenerator
->createPlaceholder($pre_bubbling_elements);
// Remember the result for this placeholder to avoid double work.
$placeholder = (string) $placeholder_element['#markup'];
$this->placeholderResultsCache[$placeholder] = $rendered_elements;
return $placeholder_element;
}
/**
* Retrieves an auto-placeholdered renderable array from the static cache.
*
* @param array $elements
* A renderable array.
*
* @return array|false
* A renderable array, with the original element and all its children pre-
* rendered, or FALSE if no cached copy of the element is available.
*/
protected function getFromPlaceholderResultsCache(array $elements) {
$placeholder_element = $this->placeholderGenerator
->createPlaceholder($elements);
$placeholder = (string) $placeholder_element['#markup'];
if (isset($this->placeholderResultsCache[$placeholder])) {
return $this->placeholderResultsCache[$placeholder];
}
return FALSE;
}
}
Classes
Title | Deprecated | Summary |
---|---|---|
PlaceholderingRenderCache | Adds automatic placeholdering to the RenderCache. |
Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.