Mercurial > hg > isophonics-drupal-site
diff core/lib/Drupal/Core/Render/PlaceholderingRenderCache.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core/lib/Drupal/Core/Render/PlaceholderingRenderCache.php Wed Nov 29 16:09:58 2017 +0000 @@ -0,0 +1,188 @@ +<?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; + } + +}