Skip to content

Instantly share code, notes, and snippets.

@LionsAd
Created February 18, 2015 15:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LionsAd/e8ffd028a3dde63b0492 to your computer and use it in GitHub Desktop.
Save LionsAd/e8ffd028a3dde63b0492 to your computer and use it in GitHub Desktop.
SmartPlaceholderCache
diff --git a/core/lib/Drupal/Core/Render/BubbleableMetadata.php b/core/lib/Drupal/Core/Render/BubbleableMetadata.php
index c56b421..748bc0b 100644
--- a/core/lib/Drupal/Core/Render/BubbleableMetadata.php
+++ b/core/lib/Drupal/Core/Render/BubbleableMetadata.php
@@ -39,6 +39,13 @@ class BubbleableMetadata {
protected $postRenderCache;
/**
+ * #placeholder metadata.
+ *
+ * @var array[]
+ */
+ protected $placeholders;
+
+ /**
* Constructs a BubbleableMetadata value object.
*
* @param string[] $tags
@@ -47,9 +54,12 @@ class BubbleableMetadata {
* An array of attached assets.
* @param array $post_render_cache
* An array of #post_render_cache metadata.
+ * @param array $placeholders
+ * An array of #cache placeholders metadata.
*/
- public function __construct(array $tags = [], array $attached = [], array $post_render_cache = []) {
+ public function __construct(array $tags = [], array $attached = [], array $post_render_cache = [], array $placeholders = []) {
$this->tags = $tags;
+ $this->placeholders = $placeholders;
$this->attached = $attached;
$this->postRenderCache = $post_render_cache;
}
@@ -70,6 +80,7 @@ public function __construct(array $tags = [], array $attached = [], array $post_
public function merge(BubbleableMetadata $other) {
$result = new BubbleableMetadata();
$result->tags = Cache::mergeTags($this->tags, $other->tags);
+ $result->placeholders = NestedArray::mergeDeep($this->placeholders, $other->placeholders);
$result->attached = drupal_merge_attached($this->attached, $other->attached);
$result->postRenderCache = NestedArray::mergeDeep($this->postRenderCache, $other->postRenderCache);
return $result;
@@ -83,6 +94,7 @@ public function merge(BubbleableMetadata $other) {
*/
public function applyTo(array &$build) {
$build['#cache']['tags'] = $this->tags;
+ $build['#cache']['placeholders'] = $this->placeholders;
$build['#attached'] = $this->attached;
$build['#post_render_cache'] = $this->postRenderCache;
}
@@ -98,6 +110,7 @@ public function applyTo(array &$build) {
public static function createFromRenderArray(array $build) {
$meta = new static();
$meta->tags = (isset($build['#cache']['tags'])) ? $build['#cache']['tags'] : [];
+ $meta->placeholders = (isset($build['#cache']['placeholders'])) ? $build['#cache']['placeholders'] : [];
$meta->attached = (isset($build['#attached'])) ? $build['#attached'] : [];
$meta->postRenderCache = (isset($build['#post_render_cache'])) ? $build['#post_render_cache'] : [];
return $meta;
diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 69e99a0..d280cab 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -64,6 +64,11 @@ class Renderer implements RendererInterface {
protected $cacheContexts;
/**
+ * The stored placeholders.
+ */
+ protected static $cachePlaceholders;
+
+ /**
* The stack containing bubbleable rendering metadata.
*
* @var \SplStack|null
@@ -184,6 +189,11 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
$this->bubbleStack();
return $elements['#markup'];
}
+
+ // Store the cache ID for later comparison.
+ if ($this->requestStack->getCurrentRequest()->isMethodSafe() && $cid = $this->createCacheID($elements)) {
+ $elements['#cache_old_id'] = $cid;
+ }
}
// If the default values for this element have not been loaded yet, populate
@@ -206,6 +216,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
// Defaults for bubbleable rendering metadata.
$elements['#cache']['tags'] = isset($elements['#cache']['tags']) ? $elements['#cache']['tags'] : array();
+ $elements['#cache']['placeholders'] = isset($elements['#cache']['placeholders']) ? $elements['#cache']['placeholders'] : array();
$elements['#attached'] = isset($elements['#attached']) ? $elements['#attached'] : array();
$elements['#post_render_cache'] = isset($elements['#post_render_cache']) ? $elements['#post_render_cache'] : array();
@@ -339,6 +350,11 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
$suffix = isset($elements['#suffix']) ? SafeMarkup::checkAdminXss($elements['#suffix']) : '';
$elements['#markup'] = $prefix . $elements['#children'] . $suffix;
+
+ // @todo This means the root call element should not have #cache to be cached properly.
+ if ($is_root_call) {
+ $this->processPlaceholders($elements);
+ }
// We've rendered this element (and its subtree!), now update the stack.
$this->updateStack($elements);
@@ -394,6 +410,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
*/
protected function resetStack() {
static::$stack = NULL;
+ static::$cachePlaceholders = NULL;
}
/**
@@ -468,6 +485,17 @@ protected function processPostRenderCache(array &$elements) {
}
}
+ protected function processPlaceholders(array &$elements) {
+ if (isset($elements['#cache']['placeholders'])) {
+ foreach ($elements['#cache']['placeholders'] as $placeholder => $element) {
+ $data = static::$cachePlaceholders[$placeholder];
+ // This is already rendered, but we want to bubble the data up to $elements.
+ $markup = $this->doRender($data);
+ $elements['#markup'] = str_replace($placeholder, $markup, $elements['#markup']);
+ }
+ }
+ }
+
/**
* Gets the cached, prerendered element of a renderable element from the cache.
*
@@ -493,12 +521,45 @@ protected function cacheGet(array $elements) {
if (!empty($cid) && $cache = $this->cacheFactory->get($bin)->get($cid)) {
$cached_element = $cache->data;
+ if (!empty($cached_element['#cache']['placeholders']) && !$this->checkPlaceholders($cached_element['#cache']['placeholders'])) {
+ return FALSE;
+ }
+
// Return the cached element.
return $cached_element;
}
return FALSE;
}
+ protected function checkPlaceholders($placeholders) {
+ $cid_map = [];
+ foreach ($placeholders as $placeholder => $elements) {
+ $cid_map[$placeholder] = $this->createCacheID($elements);
+ }
+ $cids = array_keys($cid_map);
+
+ // @todo do this per 'bin'.
+ $cached = $this->cacheFactory->get('render')->getMultiple($cids);
+
+ // A cache miss is fatal.
+ if (count($cached) != count($cid_map)) {
+ return FALSE;
+ }
+
+ $data = array();
+ // Store in this class - unless reset.
+ foreach ($cid_map as $placeholder => $cid) {
+ $cached_element = $cached[$cid]->data;
+ if (!empty($cached_element['#cache']['placeholders']) && !$this->checkPlaceholders($cached_element['#cache']['placeholders'])) {
+ return FALSE;
+ }
+ $data[$placeholder] = $cached_element;
+ }
+ static::$cachePlaceholders = array_merge(static::$cachePlaceholders, $data);
+
+ return TRUE;
+ }
+
/**
* Caches the rendered output of a renderable element.
*
@@ -530,7 +591,19 @@ protected function cacheSet(array &$elements) {
$bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'render';
$expire = isset($elements['#cache']['expire']) ? $elements['#cache']['expire'] : Cache::PERMANENT;
- $this->cacheFactory->get($bin)->set($cid, $data, $expire, $data['#cache']['tags']);
+ $cache = $this->cacheFactory->get($bin);
+
+ // This assumes bubbling worked correctly and createCacheID() working on contexts instead of cache keys.
+ if (isset($elements['#cache_old_id']) && $cid != $elements['#cache_old_id']) {
+ $cache_old_id = $elements['#cache_old_id'];
+ $placeholder = '<!-- PLACEHOLDER: ' . $cache_old_id . ' -->';
+
+ $elements = [ '#markup' => $placeholder ];
+ $elements['#cache']['placeholders'][$placeholder] = [ '#cache' => $elements['#cache'] ];
+ $this->cacheFactory->get('render')->set($cache_old_id, $elements, $expire, $data['#cache']['tags']);
+ static::$cachePlaceholders[$placeholder] = $data;
+ }
+ $cache->set($cid, $data, $expire, $data['#cache']['tags']);
}
/**
@@ -569,6 +642,7 @@ public function getCacheableRenderArray(array $elements) {
'#post_render_cache' => $elements['#post_render_cache'],
'#cache' => [
'tags' => $elements['#cache']['tags'],
+ 'placeholders' => $elements['#cache']['placeholders'],
],
];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment