From 62c31b569a2c9cc497e2bce65cd8ccac69ab60f2 Mon Sep 17 00:00:00 2001 From: Alex Pott Date: Thu, 15 Jan 2015 09:58:58 +0000 Subject: [PATCH] Issue #2379741 by Wim Leers, damiankloip: Add Renderer::getCacheableRenderArray() to encapsulate which data is needed for caching a render array and have views use it --- core/core.services.yml | 2 +- core/includes/common.inc | 102 +------------ .../Core/Render/MainContent/HtmlRenderer.php | 10 +- core/lib/Drupal/Core/Render/Renderer.php | 144 +++++++++++++++++- .../Drupal/Core/Render/RendererInterface.php | 22 ++- .../block/src/Tests/BlockViewBuilderTest.php | 13 +- .../system/src/Tests/Common/RenderTest.php | 27 ++-- .../Tests/Entity/EntityViewBuilderTest.php | 10 +- .../Plugin/views/cache/CachePluginBase.php | 80 +++++----- .../views/src/Plugin/views/cache/Time.php | 8 +- 10 files changed, 240 insertions(+), 178 deletions(-) diff --git a/core/core.services.yml b/core/core.services.yml index a9af34679c..b3c4ffe49f 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1153,4 +1153,4 @@ services: - { name: mime_type_guesser } renderer: class: Drupal\Core\Render\Renderer - arguments: ['@controller_resolver', '@theme.manager', '@plugin.manager.element_info'] + arguments: ['@controller_resolver', '@theme.manager', '@plugin.manager.element_info', '@request_stack', '@cache_factory', '@cache_contexts'] diff --git a/core/includes/common.inc b/core/includes/common.inc index 409fa67d4c..1c915e9158 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -2265,79 +2265,6 @@ function show(&$element) { return $element; } -/** - * Gets the cached, prerendered element of a renderable element from the cache. - * - * @param array $elements - * A renderable array. - * - * @return array - * A renderable array, with the original element and all its children pre- - * rendered, or FALSE if no cached copy of the element is available. - * - * @see drupal_render() - * @see drupal_render_cache_set() - */ -function drupal_render_cache_get(array $elements) { - if (!\Drupal::request()->isMethodSafe() || !$cid = drupal_render_cid_create($elements)) { - return FALSE; - } - $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'render'; - - if (!empty($cid) && $cache = \Drupal::cache($bin)->get($cid)) { - $cached_element = $cache->data; - // Return the cached element. - return $cached_element; - } - return FALSE; -} - -/** - * Caches the rendered output of a renderable element. - * - * This is called by drupal_render() if the #cache property is set on an - * element. - * - * @param $markup - * The rendered output string of $elements. - * @param array $elements - * A renderable array. - * - * @see drupal_render_cache_get() - */ -function drupal_render_cache_set(&$markup, array $elements) { - // Create the cache ID for the element. - if (!\Drupal::request()->isMethodSafe() || !$cid = drupal_render_cid_create($elements)) { - return FALSE; - } - - // Cache implementations are allowed to modify the markup, to support - // replacing markup with edge-side include commands. The supporting cache - // backend will store the markup in some other key (like - // $data['#real-value']) and return an include command instead. When the - // ESI command is executed by the content accelerator, the real value can - // be retrieved and used. - $data['#markup'] = $markup; - - // Persist attached data associated with this element. - $data['#attached'] = $elements['#attached']; - - // Persist #post_render_cache callbacks associated with this element. - if (isset($elements['#post_render_cache'])) { - $data['#post_render_cache'] = $elements['#post_render_cache']; - } - - // Persist cache tags associated with this element. Also associate the - // "rendered" cache tag. This allows us to invalidate the entire render cache, - // regardless of the cache bin. - $data['#cache']['tags'] = $elements['#cache']['tags']; - $data['#cache']['tags'][] = 'rendered'; - - $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'render'; - $expire = isset($elements['#cache']['expire']) ? $elements['#cache']['expire'] : Cache::PERMANENT; - \Drupal::cache($bin)->set($cid, $data, $expire, $data['#cache']['tags']); -} - /** * Generates a render cache placeholder. * @@ -2358,7 +2285,7 @@ function drupal_render_cache_set(&$markup, array $elements) { * * @throws \Exception * - * @see drupal_render_cache_get() + * @see \Drupal\Core\Render\Renderer::getFromCache() */ function drupal_render_cache_generate_placeholder($callback, array &$context) { if (is_string($callback) && strpos($callback, '::') === FALSE) { @@ -2382,33 +2309,6 @@ function drupal_render_cache_generate_placeholder($callback, array &$context) { return ''; } -/** - * Creates the cache ID for a renderable element. - * - * This creates the cache ID string, either by returning the #cache['cid'] - * property if present or by building the cache ID out of the #cache['keys']. - * - * @param $elements - * A renderable array. - * - * @return - * The cache ID string, or FALSE if the element may not be cached. - */ -function drupal_render_cid_create($elements) { - if (isset($elements['#cache']['cid'])) { - return $elements['#cache']['cid']; - } - elseif (isset($elements['#cache']['keys'])) { - // Cache keys may either be static (just strings) or tokens (placeholders - // that are converted to static keys by the @cache_contexts service, - // depending on the request). - $cache_contexts = \Drupal::service("cache_contexts"); - $keys = $cache_contexts->convertTokensToKeys($elements['#cache']['keys']); - return implode(':', $keys); - } - return FALSE; -} - /** * Retrieves the default properties for the defined element type. * diff --git a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php index 323fd871c3..081902ff93 100644 --- a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php +++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php @@ -185,16 +185,12 @@ protected function prepare(array $main_content, Request $request, RouteMatchInte // title. We set its $is_root_call parameter to FALSE, to ensure // #post_render_cache callbacks are not yet applied. This is essentially // "pre-rendering" the main content, the "full rendering" will happen in - // ::renderContentIntoResponse(). + // ::renderResponse(). // @todo Remove this once https://www.drupal.org/node/2359901 lands. if (!empty($main_content)) { $this->renderer->render($main_content, FALSE); - $main_content = [ - '#markup' => $main_content['#markup'], - '#attached' => $main_content['#attached'], - '#cache' => ['tags' => $main_content['#cache']['tags']], - '#post_render_cache' => $main_content['#post_render_cache'], - '#title' => isset($main_content['#title']) ? $main_content['#title'] : NULL, + $main_content = $this->renderer->getCacheableRenderArray($main_content) + [ + '#title' => isset($main_content['#title']) ? $main_content['#title'] : NULL ]; } diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php index 91e4bd94ca..39d2090de5 100644 --- a/core/lib/Drupal/Core/Render/Renderer.php +++ b/core/lib/Drupal/Core/Render/Renderer.php @@ -7,11 +7,14 @@ namespace Drupal\Core\Render; +use Drupal\Core\Cache\CacheContexts; +use Drupal\Core\Cache\CacheFactoryInterface; use Drupal\Core\Controller\ControllerResolverInterface; use Drupal\Core\Theme\ThemeManagerInterface; use Drupal\Component\Utility\SafeMarkup; use Drupal\Core\Cache\Cache; use Drupal\Component\Utility\NestedArray; +use Symfony\Component\HttpFoundation\RequestStack; /** * Turns a render array into a HTML string. @@ -39,6 +42,27 @@ class Renderer implements RendererInterface { */ protected $elementInfo; + /** + * The request stack. + * + * @var \Symfony\Component\HttpFoundation\RequestStack + */ + protected $requestStack; + + /** + * The cache factory. + * + * @var \Drupal\Core\Cache\CacheFactoryInterface + */ + protected $cacheFactory; + + /** + * The cache contexts service. + * + * @var \Drupal\Core\Cache\CacheContexts + */ + protected $cacheContexts; + /** * The stack containing bubbleable rendering metadata. * @@ -55,11 +79,20 @@ class Renderer implements RendererInterface { * The theme manager. * @param \Drupal\Core\Render\ElementInfoManagerInterface $element_info * The element info. + * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack + * The request stack. + * @param \Drupal\Core\Cache\CacheFactoryInterface $cache_factory + * The cache factory. + * @param \Drupal\Core\Cache\CacheContexts $cache_contexts + * The cache contexts service. */ - public function __construct(ControllerResolverInterface $controller_resolver, ThemeManagerInterface $theme, ElementInfoManagerInterface $element_info) { + public function __construct(ControllerResolverInterface $controller_resolver, ThemeManagerInterface $theme, ElementInfoManagerInterface $element_info, RequestStack $request_stack, CacheFactoryInterface $cache_factory, CacheContexts $cache_contexts) { $this->controllerResolver = $controller_resolver; $this->theme = $theme; $this->elementInfo = $element_info; + $this->requestStack = $request_stack; + $this->cacheFactory = $cache_factory; + $this->cacheContexts = $cache_contexts; } /** @@ -133,7 +166,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) { // Try to fetch the prerendered element from cache, run any // #post_render_cache callbacks and return the final markup. if (isset($elements['#cache'])) { - $cached_element = drupal_render_cache_get($elements); + $cached_element = $this->cacheGet($elements); if ($cached_element !== FALSE) { $elements = $cached_element; // Only when we're not in a root (non-recursive) drupal_render() call, @@ -312,7 +345,7 @@ protected function doRender(&$elements, $is_root_call = FALSE) { // Cache the processed element if #cache is set. if (isset($elements['#cache'])) { - drupal_render_cache_set($elements['#markup'], $elements); + $this->cacheSet($elements); } // Only when we're in a root (non-recursive) drupal_render() call, @@ -437,4 +470,109 @@ protected function processPostRenderCache(array &$elements) { } } + /** + * Gets the cached, prerendered element of a renderable element from the cache. + * + * @param array $elements + * A renderable array. + * + * @return array + * A renderable array, with the original element and all its children pre- + * rendered, or FALSE if no cached copy of the element is available. + * + * @see ::render() + * @see ::saveToCache() + */ + protected function cacheGet(array $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; + } + $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'render'; + + if (!empty($cid) && $cache = $this->cacheFactory->get($bin)->get($cid)) { + $cached_element = $cache->data; + // Return the cached element. + return $cached_element; + } + return FALSE; + } + + /** + * Caches the rendered output of a renderable element. + * + * This is called by ::render() if the #cache property is set on an element. + * + * @param array $elements + * A renderable array. + * + * @return bool|null + * Returns FALSE if no cache item could be created, NULL otherwise. + * + * @see ::getFromCache() + */ + protected function cacheSet(array &$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); + + // Cache tags are cached, but we also want to assocaite the "rendered" cache + // tag. This allows us to invalidate the entire render cache, regardless of + // the cache bin. + $data['#cache']['tags'][] = 'rendered'; + + $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']); + } + + /** + * Creates the cache ID for a renderable element. + * + * This creates the cache ID string, either by returning the #cache['cid'] + * property if present or by building the cache ID out of the #cache['keys']. + * + * @param array $elements + * A renderable array. + * + * @return string + * The cache ID string, or FALSE if the element may not be cached. + */ + protected function createCacheID(array $elements) { + if (isset($elements['#cache']['cid'])) { + return $elements['#cache']['cid']; + } + elseif (isset($elements['#cache']['keys'])) { + // Cache keys may either be static (just strings) or tokens (placeholders + // that are converted to static keys by the @cache_contexts service, + // depending on the request). + $keys = $this->cacheContexts->convertTokensToKeys($elements['#cache']['keys']); + return implode(':', $keys); + } + return FALSE; + } + + /** + * {@inheritdoc} + */ + public function getCacheableRenderArray(array $elements) { + return [ + '#markup' => $elements['#markup'], + '#attached' => $elements['#attached'], + '#post_render_cache' => $elements['#post_render_cache'], + '#cache' => [ + 'tags' => $elements['#cache']['tags'], + ], + ]; + } + } diff --git a/core/lib/Drupal/Core/Render/RendererInterface.php b/core/lib/Drupal/Core/Render/RendererInterface.php index 97f09b6c7f..579ce5e973 100644 --- a/core/lib/Drupal/Core/Render/RendererInterface.php +++ b/core/lib/Drupal/Core/Render/RendererInterface.php @@ -97,8 +97,7 @@ public function renderPlain(&$elements); * 'keys' is set, the cache ID is created automatically from these keys. * Cache keys may either be static (just strings) or tokens * (placeholders that are converted to static keys by the - * @cache_contexts service, depending on the request). See - * drupal_render_cid_create(). + * 'cache_contexts' service, depending on the request). * - 'cid': Specify the cache ID directly. Either 'keys' or 'cid' is * required. If 'cid' is set, 'keys' is ignored. Use only if you have * special requirements. @@ -269,4 +268,23 @@ public function renderPlain(&$elements); */ public function render(&$elements, $is_root_call = FALSE); + /** + * Gets a cacheable render array for a render array and its rendered output. + * + * Given a render array and its rendered output (HTML string), return an array + * data structure that allows the render array and its associated metadata to + * be cached reliably (and is serialization-safe). + * + * If Drupal needs additional rendering metadata to be cached at some point, + * consumers of this method will continue to work. Those who only cache + * certain parts of a render array will cease to work. + * + * @param array $elements + * A renderable array, on which ::render() has already been invoked. + * + * @return array + * An array representing the cacheable data for this render array. + */ + public function getCacheableRenderArray(array $elements); + } diff --git a/core/modules/block/src/Tests/BlockViewBuilderTest.php b/core/modules/block/src/Tests/BlockViewBuilderTest.php index bfd37d4a78..bd2a986e2d 100644 --- a/core/modules/block/src/Tests/BlockViewBuilderTest.php +++ b/core/modules/block/src/Tests/BlockViewBuilderTest.php @@ -160,7 +160,7 @@ protected function verifyRenderCacheHandling() { // Test that a cache entry is created. $build = $this->getBlockRenderArray(); - $cid = drupal_render_cid_create($build); + $cid = 'entity_view:block:test_block:en:core'; drupal_render($build); $this->assertTrue($this->container->get('cache.render')->get($cid), 'The block render element has been cached.'); @@ -223,10 +223,10 @@ public function testBlockViewBuilderAlter() { )); $alter_add_key = $this->randomMachineName(); \Drupal::state()->set('block_test_view_alter_cache_key', $alter_add_key); + $cid = 'entity_view:block:test_block:en:core:' . $alter_add_key; $expected_keys = array_merge($default_keys, array($alter_add_key)); $build = $this->getBlockRenderArray(); $this->assertIdentical($expected_keys, $build['#cache']['keys'], 'An altered cacheable block has the expected cache keys.'); - $cid = drupal_render_cid_create(array('#cache' => array('keys' => $expected_keys))); $this->assertIdentical(drupal_render($build), ''); $cache_entry = $this->container->get('cache.render')->get($cid); $this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.'); @@ -242,7 +242,6 @@ public function testBlockViewBuilderAlter() { $build = $this->getBlockRenderArray(); sort($build['#cache']['tags']); $this->assertIdentical($expected_tags, $build['#cache']['tags'], 'An altered cacheable block has the expected cache tags.'); - $cid = drupal_render_cid_create(array('#cache' => array('keys' => $expected_keys))); $this->assertIdentical(drupal_render($build), ''); $cache_entry = $this->container->get('cache.render')->get($cid); $this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.'); @@ -272,6 +271,8 @@ public function testBlockViewBuilderAlter() { * @see \Drupal\block\Tests\BlockCacheTest */ public function testBlockViewBuilderCacheContexts() { + $cache_contexts = \Drupal::service("cache_contexts"); + // Force a request via GET so we can get drupal_render() cache working. $request = \Drupal::request(); $request_method = $request->server->get('REQUEST_METHOD'); @@ -282,7 +283,7 @@ public function testBlockViewBuilderCacheContexts() { 'max_age' => 600, )); $build = $this->getBlockRenderArray(); - $cid = drupal_render_cid_create($build); + $cid = implode(':', $build['#cache']['keys']); drupal_render($build); $this->assertTrue($this->container->get('cache.render', $cid), 'The block render element has been cached.'); @@ -293,7 +294,7 @@ public function testBlockViewBuilderCacheContexts() { )); $old_cid = $cid; $build = $this->getBlockRenderArray(); - $cid = drupal_render_cid_create($build); + $cid = implode(':', $cache_contexts->convertTokensToKeys($build['#cache']['keys'])); drupal_render($build); $this->assertTrue($this->container->get('cache.render', $cid), 'The block render element has been cached.'); $this->assertNotEqual($cid, $old_cid, 'The cache ID has changed.'); @@ -306,7 +307,7 @@ public function testBlockViewBuilderCacheContexts() { $this->container->set('cache_context.url', $temp_context); $old_cid = $cid; $build = $this->getBlockRenderArray(); - $cid = drupal_render_cid_create($build); + $cid = implode(':', $cache_contexts->convertTokensToKeys($build['#cache']['keys'])); drupal_render($build); $this->assertTrue($this->container->get('cache.render', $cid), 'The block render element has been cached.'); $this->assertNotEqual($cid, $old_cid, 'The cache ID has changed.'); diff --git a/core/modules/system/src/Tests/Common/RenderTest.php b/core/modules/system/src/Tests/Common/RenderTest.php index 6848dc9b97..b137d19f27 100644 --- a/core/modules/system/src/Tests/Common/RenderTest.php +++ b/core/modules/system/src/Tests/Common/RenderTest.php @@ -525,7 +525,7 @@ function testDrupalRenderPostRenderCache() { // GET request: validate cached data. $element = array('#cache' => array('cid' => 'post_render_cache_test_GET')); - $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data; + $cached_element = \Drupal::cache('render')->get('post_render_cache_test_GET')->data; $expected_element = array( '#markup' => '

#cache enabled, GET

', '#attached' => $test_element['#attached'], @@ -566,8 +566,7 @@ function testDrupalRenderPostRenderCache() { $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the one added by the #post_render_cache callback exist.'); // POST request: Ensure no data was cached. - $element = array('#cache' => array('cid' => 'post_render_cache_test_POST')); - $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element)); + $cached_element = \Drupal::cache('render')->get('post_render_cache_test_POST'); $this->assertFalse($cached_element, 'No data is cached because this is a POST request.'); // Restore the previous request method. @@ -630,8 +629,7 @@ function testDrupalRenderChildrenPostRenderCache() { $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.'); // GET request: validate cached data. - $element = array('#cache' => $element['#cache']); - $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data; + $cached_element = \Drupal::cache('render')->get('simpletest:drupal_render:children_post_render_cache')->data; $expected_element = array( '#attached' => array( 'drupalSettings' => [ @@ -697,11 +695,8 @@ function testDrupalRenderChildrenPostRenderCache() { $this->assertIdentical($element['#attached']['drupalSettings'], $expected_js_settings, '#attached is modified; both the original JavaScript setting and the ones added by each #post_render_cache callback exist.'); // GET request: validate cached data for both the parent and child. - $element = $test_element; - $element['#cache']['keys'] = array('simpletest', 'drupal_render', 'children_post_render_cache', 'nested_cache_parent'); - $element['child']['#cache']['keys'] = array('simpletest', 'drupal_render', 'children_post_render_cache', 'nested_cache_child'); - $cached_parent_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data; - $cached_child_element = \Drupal::cache('render')->get(drupal_render_cid_create($element['child']))->data; + $cached_parent_element = \Drupal::cache('render')->get('simpletest:drupal_render:children_post_render_cache:nested_cache_parent')->data; + $cached_child_element = \Drupal::cache('render')->get('simpletest:drupal_render:children_post_render_cache:nested_cache_child')->data; $expected_parent_element = array( '#attached' => array( 'drupalSettings' => [ @@ -834,8 +829,7 @@ function testDrupalRenderRenderCachePlaceholder() { // GET request: validate cached data. $expected_token = $context['token']; - $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_GET')); - $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data; + $cached_element = \Drupal::cache('render')->get('render_cache_placeholder_test_GET')->data; // Parse unique token out of the cached markup. $dom = Html::load($cached_element['#markup']); $xpath = new \DOMXPath($dom); @@ -927,8 +921,7 @@ function testDrupalRenderChildElementRenderCachePlaceholder() { // GET request: validate cached data for child element. $expected_token = $context['token']; - $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_child_GET')); - $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data; + $cached_element = \Drupal::cache('render')->get('render_cache_placeholder_test_child_GET')->data; // Parse unique token out of the cached markup. $dom = Html::load($cached_element['#markup']); $xpath = new \DOMXPath($dom); @@ -953,8 +946,7 @@ function testDrupalRenderChildElementRenderCachePlaceholder() { $this->assertIdentical($cached_element, $expected_element, 'The correct data is cached for the child element: the stored #markup and #attached properties are not affected by #post_render_cache callbacks.'); // GET request: validate cached data (for the parent/entire render array). - $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_GET')); - $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data; + $cached_element = \Drupal::cache('render')->get('render_cache_placeholder_test_GET')->data; // Parse unique token out of the cached markup. $dom = Html::load($cached_element['#markup']); $xpath = new \DOMXPath($dom); @@ -981,8 +973,7 @@ function testDrupalRenderChildElementRenderCachePlaceholder() { // GET request: validate cached data. // Check the cache of the child element again after the parent has been // rendered. - $element = array('#cache' => array('cid' => 'render_cache_placeholder_test_child_GET')); - $cached_element = \Drupal::cache('render')->get(drupal_render_cid_create($element))->data; + $cached_element = \Drupal::cache('render')->get('render_cache_placeholder_test_child_GET')->data; // Verify that the child element contains the correct // render_cache_placeholder markup. $dom = Html::load($cached_element['#markup']); diff --git a/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php b/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php index d57b79c663..2535bc7ebb 100644 --- a/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php +++ b/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php @@ -33,6 +33,8 @@ protected function setUp() { * Tests entity render cache handling. */ public function testEntityViewBuilderCache() { + $cache_contexts = \Drupal::service("cache_contexts"); + // Force a request via GET so we can get drupal_render() cache working. $request = \Drupal::request(); $request_method = $request->server->get('REQUEST_METHOD'); @@ -48,7 +50,7 @@ public function testEntityViewBuilderCache() { // Get a fully built entity view render array. $entity_test->save(); $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full'); - $cid = drupal_render_cid_create($build); + $cid = implode(':', $cache_contexts->convertTokensToKeys($build['#cache']['keys'])); $bin = $build['#cache']['bin']; // Mock the build array to not require the theme registry. @@ -79,6 +81,8 @@ public function testEntityViewBuilderCache() { * Tests entity render cache with references. */ public function testEntityViewBuilderCacheWithReferences() { + $cache_contexts = \Drupal::service("cache_contexts"); + // Force a request via GET so we can get drupal_render() cache working. $request = \Drupal::request(); $request_method = $request->server->get('REQUEST_METHOD'); @@ -95,7 +99,7 @@ public function testEntityViewBuilderCacheWithReferences() { // Get a fully built entity view render array for the referenced entity. $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test_reference, 'full'); - $cid_reference = drupal_render_cid_create($build); + $cid_reference = implode(':', $cache_contexts->convertTokensToKeys($build['#cache']['keys'])); $bin_reference = $build['#cache']['bin']; // Mock the build array to not require the theme registry. @@ -114,7 +118,7 @@ public function testEntityViewBuilderCacheWithReferences() { // Get a fully built entity view render array. $build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full'); - $cid = drupal_render_cid_create($build); + $cid = implode(':', $cache_contexts->convertTokensToKeys($build['#cache']['keys'])); $bin = $build['#cache']['bin']; // Mock the build array to not require the theme registry. diff --git a/core/modules/views/src/Plugin/views/cache/CachePluginBase.php b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php index bce200d8d5..56c0fbb0f7 100644 --- a/core/modules/views/src/Plugin/views/cache/CachePluginBase.php +++ b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php @@ -7,10 +7,11 @@ namespace Drupal\views\Plugin\views\cache; -use Drupal\Component\Utility\NestedArray; use Drupal\Core\Cache\Cache; +use Drupal\Core\Render\RendererInterface; use Drupal\views\Plugin\views\PluginBase; use Drupal\Core\Database\Query\Select; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * @defgroup views_cache_plugins Views cache plugins @@ -72,6 +73,43 @@ abstract class CachePluginBase extends PluginBase { */ protected $outputKey; + /** + * The renderer service. + * + * @var \Drupal\Core\Render\RendererInterface + */ + protected $renderer; + + /** + * Constructs a CachePluginBase object. + * + * @param array $configuration + * A configuration array containing information about the plugin instance. + * @param string $plugin_id + * The plugin_id for the plugin instance. + * @param mixed $plugin_definition + * The plugin implementation definition. + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer service. + */ + public function __construct(array $configuration, $plugin_id, $plugin_definition, RendererInterface $renderer) { + parent::__construct($configuration, $plugin_id, $plugin_definition); + + $this->renderer = $renderer; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static( + $configuration, + $plugin_id, + $plugin_definition, + $container->get('renderer') + ); + } + /** * Returns the outputKey property. * @@ -143,8 +181,8 @@ public function cacheSet($type) { \Drupal::cache($this->resultsBin)->set($this->generateResultsKey(), $data, $this->cacheSetExpire($type), $this->getCacheTags()); break; case 'output': - $this->storage['output'] = drupal_render($this->view->display_handler->output); - $this->gatherRenderMetadata($this->view->display_handler->output); + $this->renderer->render($this->view->display_handler->output); + $this->storage = $this->renderer->getCacheableRenderArray($this->view->display_handler->output); \Drupal::cache($this->outputBin)->set($this->generateOutputKey(), $this->storage, $this->cacheSetExpire($type), $this->getCacheTags()); break; } @@ -180,17 +218,10 @@ public function cacheGet($type) { if ($cache = \Drupal::cache($this->outputBin)->get($this->generateOutputKey())) { if (!$cutoff || $cache->created > $cutoff) { $this->storage = $cache->data; - - $this->restoreRenderMetadata(); - $this->view->display_handler->output = array( - '#attached' => &$this->view->element['#attached'], - '#cache' => [ - 'tags' => &$this->view->element['#cache']['tags'], - ], - '#post_render_cache' => &$this->view->element['#post_render_cache'], - '#markup' => $cache->data['output'], - ); - + $this->view->display_handler->output = $this->storage; + $this->view->element['#attached'] = &$this->view->display_handler->output['#attached']; + $this->view->element['#cache']['tags'] = &$this->view->display_handler->output['#cache']['tags']; + $this->view->element['#post_render_cache'] = &$this->view->display_handler->output['#post_render_cache']; return TRUE; } } @@ -235,27 +266,6 @@ public function postRender(&$output) { } */ public function cacheStart() { } - /** - * Gather bubbleable render metadata from the render array. - * - * @param array $render_array - * The view render array to collect data from. - */ - protected function gatherRenderMetadata(array $render_array = []) { - $this->storage['attachments'] = $render_array['#attached']; - $this->storage['postRenderCache'] = $render_array['#post_render_cache']; - $this->storage['cacheTags'] = $render_array['#cache']['tags']; - } - - /** - * Restore bubbleable render metadata. - */ - public function restoreRenderMetadata() { - $this->view->element['#attached'] = drupal_merge_attached($this->view->element['#attached'], $this->storage['attachments']); - $this->view->element['#cache']['tags'] = Cache::mergeTags(isset($this->view->element['#cache']['tags']) ? $this->view->element['#cache']['tags'] : [], $this->storage['cacheTags']); - $this->view->element['#post_render_cache'] = NestedArray::mergeDeep(isset($this->view->element['#post_render_cache']) ? $this->view->element['#post_render_cache'] : [], $this->storage['postRenderCache']); - } - /** * Calculates and sets a cache ID used for the result cache. * diff --git a/core/modules/views/src/Plugin/views/cache/Time.php b/core/modules/views/src/Plugin/views/cache/Time.php index b318cfa2e9..3c43755a6d 100644 --- a/core/modules/views/src/Plugin/views/cache/Time.php +++ b/core/modules/views/src/Plugin/views/cache/Time.php @@ -9,6 +9,7 @@ use Drupal\Core\Datetime\DateFormatter; use Drupal\Core\Cache\Cache; +use Drupal\Core\Render\RendererInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Drupal\Core\Form\FormStateInterface; @@ -46,12 +47,14 @@ class Time extends CachePluginBase { * The plugin_id for the plugin instance. * @param mixed $plugin_definition * The plugin implementation definition. + * @param \Drupal\Core\Render\RendererInterface $renderer + * The renderer service. * @param \Drupal\Core\Datetime\DateFormatter $date_formatter * The date formatter service. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, DateFormatter $date_formatter) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, RendererInterface $renderer, DateFormatter $date_formatter) { $this->dateFormatter = $date_formatter; - parent::__construct($configuration, $plugin_id, $plugin_definition); + parent::__construct($configuration, $plugin_id, $plugin_definition, $renderer); } /** @@ -62,6 +65,7 @@ public static function create(ContainerInterface $container, array $configuratio $configuration, $plugin_id, $plugin_definition, + $container->get('renderer'), $container->get('date.formatter') ); } -- GitLab