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;
+  }
+
+}