Commit 2847b598 authored by alexpott's avatar alexpott

Issue #2453059 by Wim Leers: Set default render cache contexts: 'theme' +...

Issue #2453059 by Wim Leers: Set default render cache contexts: 'theme' + 'languages:' . LanguageInterface::TYPE_INTERFACE
parent ea081822
parameters:
session.storage.options: {}
twig.config: {}
renderer.config:
required_cache_contexts: ['languages:language_interface', 'theme']
factory.keyvalue:
default: keyvalue.database
factory.keyvalue.expirable:
......@@ -1333,6 +1335,6 @@ services:
lazy: true
renderer:
class: Drupal\Core\Render\Renderer
arguments: ['@controller_resolver', '@theme.manager', '@plugin.manager.element_info', '@request_stack', '@cache_factory', '@cache_contexts']
arguments: ['@controller_resolver', '@theme.manager', '@plugin.manager.element_info', '@request_stack', '@cache_factory', '@cache_contexts', '%renderer.config%']
email.validator:
class: Egulias\EmailValidator\EmailValidator
......@@ -170,7 +170,6 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco
'#cache' => array(
'tags' => Cache::mergeTags($this->getCacheTags(), $entity->getCacheTags()),
'contexts' => [
'theme',
'user.roles',
],
),
......
......@@ -64,6 +64,13 @@ class Renderer implements RendererInterface {
*/
protected $cacheContexts;
/**
* The renderer configuration array.
*
* @var array
*/
protected $rendererConfig;
/**
* The stack containing bubbleable rendering metadata.
*
......@@ -86,14 +93,17 @@ class Renderer implements RendererInterface {
* The cache factory.
* @param \Drupal\Core\Cache\CacheContexts $cache_contexts
* The cache contexts service.
* @param array $renderer_config
* The renderer configuration array.
*/
public function __construct(ControllerResolverInterface $controller_resolver, ThemeManagerInterface $theme, ElementInfoManagerInterface $element_info, RequestStack $request_stack, CacheFactoryInterface $cache_factory, CacheContexts $cache_contexts) {
public function __construct(ControllerResolverInterface $controller_resolver, ThemeManagerInterface $theme, ElementInfoManagerInterface $element_info, RequestStack $request_stack, CacheFactoryInterface $cache_factory, CacheContexts $cache_contexts, array $renderer_config) {
$this->controllerResolver = $controller_resolver;
$this->theme = $theme;
$this->elementInfo = $element_info;
$this->requestStack = $request_stack;
$this->cacheFactory = $cache_factory;
$this->cacheContexts = $cache_contexts;
$this->rendererConfig = $renderer_config;
}
/**
......@@ -164,10 +174,25 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
}
static::$stack->push(new BubbleableMetadata());
// Set the bubbleable rendering metadata that has configurable defaults, if:
// - this is the root call, to ensure that the final render array definitely
// has these configurable defaults, even when no subtree is render cached.
// - this is a render cacheable subtree, to ensure that the cached data has
// the configurable defaults (which may affect the ID and invalidation).
if ($is_root_call || isset($elements['#cache']['keys'])) {
$required_cache_contexts = $this->rendererConfig['required_cache_contexts'];
if (isset($elements['#cache']['contexts'])) {
$elements['#cache']['contexts'] = Cache::mergeContexts($elements['#cache']['contexts'], $required_cache_contexts);
}
else {
$elements['#cache']['contexts'] = $required_cache_contexts;
}
}
// Try to fetch the prerendered element from cache, run any
// #post_render_cache callbacks and return the final markup.
$pre_bubbling_cid = NULL;
if (isset($elements['#cache'])) {
if (isset($elements['#cache']['keys'])) {
$cached_element = $this->cacheGet($elements);
if ($cached_element !== FALSE) {
$elements = $cached_element;
......@@ -216,7 +241,6 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
}
// Defaults for bubbleable rendering metadata.
$elements['#cache']['contexts'] = isset($elements['#cache']['contexts']) ? $elements['#cache']['contexts'] : array();
$elements['#cache']['tags'] = isset($elements['#cache']['tags']) ? $elements['#cache']['tags'] : array();
$elements['#cache']['max-age'] = isset($elements['#cache']['max-age']) ? $elements['#cache']['max-age'] : Cache::PERMANENT;
$elements['#attached'] = isset($elements['#attached']) ? $elements['#attached'] : array();
......
......@@ -36,12 +36,6 @@ public function view(EntityInterface $entity, $view_mode = 'full', $langcode = N
* {@inheritdoc}
*/
public function viewMultiple(array $entities = array(), $view_mode = 'full', $langcode = NULL) {
// @todo Remove when https://www.drupal.org/node/2453059 lands.
$default_cache_contexts = [
'languages',
'theme',
];
/** @var \Drupal\block\BlockInterface[] $entities */
$build = array();
foreach ($entities as $entity) {
......@@ -70,7 +64,7 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la
'#derivative_plugin_id' => $derivative_id,
'#id' => $entity->id(),
'#cache' => [
'contexts' => Cache::mergeContexts($default_cache_contexts, $plugin->getCacheContexts()),
'contexts' => $plugin->getCacheContexts(),
'tags' => Cache::mergeTags(
$this->getCacheTags(), // Block view builder cache tag.
$entity->getCacheTags(), // Block entity cache tag.
......
......@@ -9,6 +9,7 @@
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\system\Tests\Entity\EntityCacheTagsTestBase;
use Symfony\Component\HttpFoundation\Request;
......@@ -80,7 +81,7 @@ public function testBlock() {
// Expected keys, contexts, and tags for the block.
// @see \Drupal\block\BlockViewBuilder::viewMultiple()
$expected_block_cache_keys = ['entity_view', 'block', $block->id()];
$expected_block_cache_contexts = ['languages', 'theme'];
$expected_block_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'];
$expected_block_cache_tags = Cache::mergeTags(['block_view', 'rendered'], $block->getCacheTags(), $block->getPlugin()->getCacheTags());
// Expected contexts and tags for the BlockContent entity.
......
......@@ -261,6 +261,9 @@ function testProcessedTextElement() {
$expected_cache_contexts = [
// The cache context set by the filter_test_cache_contexts filter.
'languages:' . LanguageInterface::TYPE_CONTENT,
// The default cache contexts for Renderer.
'languages:' . LanguageInterface::TYPE_INTERFACE,
'theme',
];
$this->assertEqual($expected_cache_contexts, $build['#cache']['contexts'], 'Expected cache contexts present.');
$expected_markup = '<p>Hello, world!</p><p>This is a dynamic llama.</p>';
......
......@@ -7,6 +7,7 @@
namespace Drupal\node\Tests;
use Drupal\Core\Language\LanguageInterface;
use Drupal\simpletest\KernelTestBase;
/**
......@@ -36,9 +37,9 @@ public function testCacheContexts() {
$list_builder = $this->container->get('entity.manager')->getListBuilder('node');
$build = $list_builder->render();
$this->container->get('renderer')->render($build);
$this->container->get('renderer')->renderRoot($build);
$this->assertEqual(['url.query_args.pagers:0', 'user.node_grants:view'], $build['#cache']['contexts']);
$this->assertEqual(['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'url.query_args.pagers:0', 'user.node_grants:view'], $build['#cache']['contexts']);
}
}
......@@ -8,6 +8,7 @@
namespace Drupal\node\Tests\Views;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Url;
use Drupal\node\Entity\Node;
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
......@@ -241,7 +242,13 @@ protected function assertFrontPageViewCacheTags($do_assert_views_caches) {
$view = Views::getView('frontpage');
$view->setDisplay('page_1');
$cache_contexts = ['user.node_grants:view', 'languages'];
$cache_contexts = [
// Cache contexts associated with the view.
'user.node_grants:view',
'languages:' . LanguageInterface::TYPE_INTERFACE,
// Default cache contexts of the renderer.
'theme',
];
// Test before there are any nodes.
$empty_node_listing_cache_tags = [
......@@ -280,7 +287,6 @@ protected function assertFrontPageViewCacheTags($do_assert_views_caches) {
$node->save();
}
$cache_contexts = Cache::mergeContexts($cache_contexts, [
'theme',
'timezone',
'user.roles'
]);
......
......@@ -7,6 +7,7 @@
namespace Drupal\system\Tests\Cache;
use Drupal\Core\Language\LanguageInterface;
use Drupal\simpletest\WebTestBase;
/**
......@@ -68,7 +69,7 @@ function testPageCacheTags() {
));
$cache_contexts = [
'languages',
'languages:' . LanguageInterface::TYPE_INTERFACE,
'route.menu_active_trails:account',
'route.menu_active_trails:footer',
'route.menu_active_trails:main',
......
......@@ -10,6 +10,7 @@
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Entity\FieldConfig;
......@@ -315,7 +316,8 @@ public function testReferencedEntity() {
$nonempty_entity_listing_url = Url::fromRoute('entity.entity_test.collection_labels_alphabetically', ['entity_type_id' => $entity_type]);
// The default cache contexts for rendered entities.
$entity_cache_contexts = ['theme', 'user.roles'];
$default_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'];
$entity_cache_contexts = Cache::mergeContexts($default_cache_contexts, ['user.roles']);
// Cache tags present on every rendered page.
$page_cache_tags = Cache::mergeTags(
......@@ -395,7 +397,7 @@ public function testReferencedEntity() {
$this->verifyPageCache($empty_entity_listing_url, 'HIT', $empty_entity_listing_cache_tags);
// Verify the entity type's list cache contexts are present.
$contexts_in_header = $this->drupalGetHeader('X-Drupal-Cache-Contexts');
$this->assertEqual($this->getAdditionalCacheContextsForEntityListing(), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
$this->assertEqual(Cache::mergeContexts($default_cache_contexts, $this->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
$this->pass("Test listing containing referenced entity.", 'Debug');
......@@ -405,7 +407,7 @@ public function testReferencedEntity() {
$this->verifyPageCache($nonempty_entity_listing_url, 'HIT', $nonempty_entity_listing_cache_tags);
// Verify the entity type's list cache contexts are present.
$contexts_in_header = $this->drupalGetHeader('X-Drupal-Cache-Contexts');
$this->assertEqual($this->getAdditionalCacheContextsForEntityListing(), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
$this->assertEqual(Cache::mergeContexts($default_cache_contexts, $this->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
// Verify that after modifying the referenced entity, there is a cache miss
......
......@@ -7,6 +7,7 @@
namespace Drupal\system\Tests\Entity;
use Drupal\Core\Language\LanguageInterface;
use Drupal\simpletest\WebTestBase;
/**
......@@ -64,9 +65,9 @@ public function testCacheContexts() {
$list_builder = $this->container->get('entity.manager')->getListBuilder('entity_test');
$build = $list_builder->render();
$this->container->get('renderer')->render($build);
$this->container->get('renderer')->renderRoot($build);
$this->assertEqual(['entity_test_view_grants', 'url.query_args.pagers:0'], $build['#cache']['contexts']);
$this->assertEqual(['entity_test_view_grants', 'languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'url.query_args.pagers:0'], $build['#cache']['contexts']);
}
}
......@@ -7,7 +7,9 @@
namespace Drupal\system\Tests\Entity;
use Drupal\Core\Language\LanguageInterface;
use Drupal\entity_reference\Tests\EntityReferenceTestTrait;
use Drupal\Core\Cache\Cache;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
......@@ -61,7 +63,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_parts = array_merge($build['#cache']['keys'], $cache_contexts->convertTokensToKeys($build['#cache']['contexts']));
$cid_parts = array_merge($build['#cache']['keys'], $cache_contexts->convertTokensToKeys(Cache::mergeContexts($build['#cache']['contexts'], ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'])));
$cid = implode(':', $cid_parts);
$bin = $build['#cache']['bin'];
......@@ -111,7 +113,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_parts = array_merge($build['#cache']['keys'], $cache_contexts->convertTokensToKeys($build['#cache']['contexts']));
$cid_parts = array_merge($build['#cache']['keys'], $cache_contexts->convertTokensToKeys(Cache::mergeContexts($build['#cache']['contexts'], ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'])));
$cid_reference = implode(':', $cid_parts);
$bin_reference = $build['#cache']['bin'];
......@@ -130,7 +132,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_parts = array_merge($build['#cache']['keys'], $cache_contexts->convertTokensToKeys($build['#cache']['contexts']));
$cid_parts = array_merge($build['#cache']['keys'], $cache_contexts->convertTokensToKeys(Cache::mergeContexts($build['#cache']['contexts'], ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme'])));
$cid = implode(':', $cid_parts);
$bin = $build['#cache']['bin'];
......
......@@ -8,6 +8,7 @@
namespace Drupal\system\Tests\Entity;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Language\LanguageInterface;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Entity\FieldConfig;
......@@ -31,7 +32,7 @@ public function testEntityUri() {
$view_mode = $this->selectViewMode($entity_type);
// The default cache contexts for rendered entities.
$entity_cache_contexts = ['theme', 'user.roles'];
$entity_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.roles'];
// Generate the standardized entity cache tags.
$cache_tag = $this->entity->getCacheTags();
......
......@@ -11,6 +11,7 @@
use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\Entity\ConfigEntityBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\views\Views;
use Drupal\views\ViewEntityInterface;
......@@ -316,9 +317,9 @@ protected function addCacheMetadata() {
$executable->setDisplay($display_id);
list($display['cache_metadata']['cacheable'], $display['cache_metadata']['contexts']) = $executable->getDisplay()->calculateCacheMetadata();
// Always include at least the 'languages' context as there will most
// Always include at least the 'languages:' context as there will most
// probably be translatable strings in the view output.
$display['cache_metadata']['contexts'] = Cache::mergeContexts($display['cache_metadata']['contexts'], ['languages']);
$display['cache_metadata']['contexts'] = Cache::mergeContexts($display['cache_metadata']['contexts'], ['languages:' . LanguageInterface::TYPE_INTERFACE]);
}
// Restore the previous active display.
$executable->setDisplay($current_display);
......
......@@ -8,6 +8,7 @@
namespace Drupal\views\Tests;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Url;
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
use Drupal\views\Views;
......@@ -87,7 +88,7 @@ public function testGlossaryView() {
// Verify cache tags.
$this->enablePageCaching();
$this->assertPageCacheContextsAndTags(Url::fromRoute('view.glossary.page_1'), ['languages', 'url', 'user.node_grants:view'], [
$this->assertPageCacheContextsAndTags(Url::fromRoute('view.glossary.page_1'), ['languages', 'theme', 'url', 'user.node_grants:view'], [
'config:views.view.glossary',
'node:' . $nodes_by_char['a'][0]->id(), 'node:' . $nodes_by_char['a'][1]->id(), 'node:' . $nodes_by_char['a'][2]->id(),
'node_list',
......
......@@ -8,6 +8,7 @@
namespace Drupal\views\Tests;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Language\LanguageInterface;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\views\Views;
use Drupal\views\Entity\View;
......@@ -221,7 +222,7 @@ public function testViewAddCacheMetadata() {
$view = View::load('test_display');
$view->save();
$this->assertEqual(['languages', 'user.node_grants:view'], $view->getDisplay('default')['cache_metadata']['contexts']);
$this->assertEqual(['languages', 'languages:' . LanguageInterface::TYPE_INTERFACE, 'user.node_grants:view'], $view->getDisplay('default')['cache_metadata']['contexts']);
}
}
......@@ -9,6 +9,7 @@
use Drupal\Core\KeyValueStore\KeyValueMemoryFactory;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\Renderer;
use Drupal\Core\State\State;
use Drupal\Core\Cache\Cache;
......@@ -22,6 +23,10 @@ class RendererBubblingTest extends RendererTestBase {
* {@inheritdoc}
*/
protected function setUp() {
// Disable the required cache contexts, so that this test can test just the
// bubbling behavior.
$this->rendererConfig['required_cache_contexts'] = [];
parent::setUp();
$this->setUpRequest();
......@@ -249,21 +254,6 @@ public function testConditionalCacheContextBubblingSelfHealing() {
$this->setUpRequest();
$this->setupMemoryCache();
$this->cacheContexts->expects($this->any())
->method('convertTokensToKeys')
->willReturnCallback(function($context_tokens) {
global $current_user_role;
$keys = [];
foreach ($context_tokens as $context_id) {
if ($context_id === 'user.roles') {
$keys[] = 'r.' . $current_user_role;
}
else {
$keys[] = $context_id;
}
}
return $keys;
});
$test_element = [
'#cache' => [
......
......@@ -17,6 +17,17 @@
*/
class RendererPostRenderCacheTest extends RendererTestBase {
/**
* {@inheritdoc}
*/
protected function setUp() {
// Disable the required cache contexts, so that this test can test just the
// #post_render_cache behavior.
$this->rendererConfig['required_cache_contexts'] = [];
parent::setUp();
}
/**
* Generates an element with a #post_render_cache callback.
*
......
......@@ -19,7 +19,10 @@ class RendererTest extends RendererTestBase {
protected $defaultThemeVars = [
'#cache' => [
'contexts' => [],
'contexts' => [
'languages:language_interface',
'theme',
],
'tags' => [],
'max-age' => Cache::PERMANENT,
],
......@@ -499,7 +502,7 @@ public function testRenderWithoutThemeArguments() {
->willReturn('foobar');
// Test that defaults work.
$this->assertEquals($this->renderer->render($element), 'foobar', 'Defaults work');
$this->assertEquals($this->renderer->renderRoot($element), 'foobar', 'Defaults work');
}
/**
......@@ -521,7 +524,7 @@ public function testRenderWithThemeArguments() {
});
// Tests that passing arguments to the theme function works.
$this->assertEquals($this->renderer->render($element), $element['#foo'] . $element['#bar'], 'Passing arguments to theme functions works');
$this->assertEquals($this->renderer->renderRoot($element), $element['#foo'] . $element['#bar'], 'Passing arguments to theme functions works');
}
/**
......@@ -573,7 +576,7 @@ public function testRenderCache() {
$this->assertEquals($expected_tags, $element['#cache']['tags'], 'Cache tags were collected from the element and its subchild.');
// The cache item also has a 'rendered' cache tag.
$cache_item = $this->cacheFactory->get('render')->get('render_cache_test');
$cache_item = $this->cacheFactory->get('render')->get('render_cache_test:en:stark');
$this->assertSame(Cache::mergeTags($expected_tags, ['rendered']), $cache_item->tags);
}
......@@ -599,7 +602,7 @@ public function testRenderCacheMaxAge($max_age, $is_render_cached, $render_cache
];
$this->renderer->render($element);
$cache_item = $this->cacheFactory->get('render')->get('render_cache_test');
$cache_item = $this->cacheFactory->get('render')->get('render_cache_test:en:stark');
if (!$is_render_cached) {
$this->assertFalse($cache_item);
}
......
......@@ -9,6 +9,7 @@
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\MemoryBackend;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\Renderer;
use Drupal\Tests\UnitTestCase;
......@@ -69,6 +70,18 @@ class RendererTestBase extends UnitTestCase {
*/
protected $memoryCache;
/**
* The mocked renderer configuration.
*
* @var array
*/
protected $rendererConfig = [
'required_cache_contexts' => [
'languages:language_interface',
'theme',
],
];
/**
* {@inheritdoc}
*/
......@@ -83,7 +96,29 @@ protected function setUp() {
$this->cacheContexts = $this->getMockBuilder('Drupal\Core\Cache\CacheContexts')
->disableOriginalConstructor()
->getMock();
$this->renderer = new Renderer($this->controllerResolver, $this->themeManager, $this->elementInfo, $this->requestStack, $this->cacheFactory, $this->cacheContexts);
$this->cacheContexts->expects($this->any())
->method('convertTokensToKeys')
->willReturnCallback(function($context_tokens) {
global $current_user_role;
$keys = [];
foreach ($context_tokens as $context_id) {
switch ($context_id) {
case 'user.roles':
$keys[] = 'r.' . $current_user_role;
break;
case 'languages:language_interface':
$keys[] = 'en';
break;
case 'theme':
$keys[] = 'stark';
break;
default:
$keys[] = $context_id;
}
}
return $keys;
});
$this->renderer = new Renderer($this->controllerResolver, $this->themeManager, $this->elementInfo, $this->requestStack, $this->cacheFactory, $this->cacheContexts, $this->rendererConfig);
$container = new ContainerBuilder();
$container->set('cache_contexts', $this->cacheContexts);
......
......@@ -76,6 +76,14 @@ parameters:
# Not recommended in production environments
# @default true
cache: true
renderer.config:
# Renderer required cache contexts:
#
# The Renderer will automatically associate these cache contexts with every
# render array, hence varying every render array by these cache contexts.
#
# @default ['languages:language_interface', 'theme']
required_cache_contexts: ['languages:language_interface', 'theme']
factory.keyvalue:
{}
# Default key/value storage service to use.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment