Commit 0fe1e7da authored by alexpott's avatar alexpott

Issue #2445743 by dawehner, Wim Leers: Allow views base tables and entity...

Issue #2445743 by dawehner, Wim Leers: Allow views base tables and entity types to define additional cache contexts
parent 6dfbcdd0
......@@ -218,6 +218,9 @@ public function render() {
'#title' => $this->getTitle(),
'#rows' => array(),
'#empty' => $this->t('There is no @label yet.', array('@label' => $this->entityType->getLabel())),
'#cache' => [
'contexts' => $this->entityType->getListCacheContexts(),
],
);
foreach ($this->load() as $entity) {
if ($row = $this->buildRow($entity)) {
......
......@@ -202,12 +202,19 @@ class EntityType implements EntityTypeInterface {
*/
protected $field_ui_base_route;
/**
* The list cache contexts for this entity type.
*
* @var string[]
*/
protected $list_cache_contexts = [];
/**
* The list cache tags for this entity type.
*
* @var array
* @var string[]
*/
protected $list_cache_tags = array();
protected $list_cache_tags = [];
/**
* Constructs a new EntityType.
......@@ -693,6 +700,13 @@ public function getGroupLabel() {
return !empty($this->group_label) ? (string) $this->group_label : $this->t('Other', array(), array('context' => 'Entity type group'));
}
/**
* {@inheritdoc}
*/
public function getListCacheContexts() {
return $this->list_cache_contexts;
}
/**
* {@inheritdoc}
*/
......
......@@ -655,6 +655,17 @@ public function getUriCallback();
*/
public function setUriCallback($callback);
/**
* The list cache contexts associated with this entity type.
*
* Enables code listing entities of this type to ensure that rendered listings
* are varied as necessary, typically to ensure users of role A see other
* entities listed as users of role B.
*
* @return string[]
*/
public function getListCacheContexts();
/**
* The list cache tags associated with this entity type.
*
......
......@@ -45,6 +45,7 @@
* revision_table = "node_revision",
* revision_data_table = "node_field_revision",
* translatable = TRUE,
* list_cache_contexts = { "node_view_grants" },
* entity_keys = {
* "id" = "nid",
* "revision" = "vid",
......
......@@ -58,4 +58,11 @@ protected function getAdditionalCacheTagsForEntity(EntityInterface $node) {
return array('user:' . $node->getOwnerId(), 'user_view');
}
/**
* {@inheritdoc}
*/
protected function getAdditionalCacheContextsForEntityListing() {
return ['node_view_grants'];
}
}
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeListBuilderTest.
*/
namespace Drupal\node\Tests;
use Drupal\simpletest\KernelTestBase;
/**
* Tests the admin listing fallback when views is not enabled.
*
* @group node
*/
class NodeListBuilderTest extends KernelTestBase {
/**
* {@inheritdoc}
*/
public static $modules = ['node', 'user'];
protected function setUp() {
parent::setUp();
$this->installEntitySchema('node');
}
/**
* Tests that the correct cache contexts are set.
*/
public function testCacheContexts() {
/** @var \Drupal\Core\Entity\EntityListBuilderInterface $list_builder */
$list_builder = $this->container->get('entity.manager')->getListBuilder('node');
$build = $list_builder->render();
$this->container->get('renderer')->render($build);
$this->assertEqual(['node_view_grants', 'pager:0'], $build['#cache']['contexts']);
}
}
......@@ -241,7 +241,7 @@ protected function assertFrontPageViewCacheTags($do_assert_views_caches) {
$view = Views::getView('frontpage');
$view->setDisplay('page_1');
$cache_contexts = [];
$cache_contexts = ['node_view_grants', 'language'];
// Test before there are any nodes.
$empty_node_listing_cache_tags = [
......
......@@ -156,6 +156,16 @@ protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
return array();
}
/**
* Returns the additional cache tags for the tested entity's listing by type.
*
* @return string[]
* An array of the additional cache contexts.
*/
protected function getAdditionalCacheContextsForEntityListing() {
return [];
}
/**
* Returns the additional cache tags for the tested entity's listing by type.
*
......@@ -382,13 +392,19 @@ public function testReferencedEntity() {
$this->verifyPageCache($empty_entity_listing_url, 'MISS');
// Verify a cache hit, but also the presence of the correct cache tags.
$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->pass("Test listing containing referenced entity.", 'Debug');
// Prime the page cache for the listing containing the referenced entity.
$this->verifyPageCache($nonempty_entity_listing_url, 'MISS');
$this->verifyPageCache($nonempty_entity_listing_url, 'MISS', $nonempty_entity_listing_cache_tags);
// Verify a cache hit, but also the presence of the correct cache tags.
$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));
// Verify that after modifying the referenced entity, there is a cache miss
......
......@@ -56,4 +56,17 @@ public function testPager() {
$this->assertRaw('Test entity 51', 'Test entity 51 is shown.');
}
/**
* Tests that the correct cache contexts are set.
*/
public function testCacheContexts() {
/** @var \Drupal\Core\Entity\EntityListBuilderInterface $list_builder */
$list_builder = $this->container->get('entity.manager')->getListBuilder('entity_test');
$build = $list_builder->render();
$this->container->get('renderer')->render($build);
$this->assertEqual(['entity_test_view_grants', 'pager:0'], $build['#cache']['contexts']);
}
}
......@@ -162,6 +162,7 @@ public function listEntitiesAlphabetically($entity_type_id) {
'#items' => $labels,
'#title' => $entity_type_id . ' entities',
'#cache' => [
'contexts' => $entity_type_definition->getListCacheContexts(),
'tags' => $cache_tags,
],
];
......@@ -182,11 +183,13 @@ public function listEntitiesAlphabetically($entity_type_id) {
* A renderable array.
*/
public function listEntitiesEmpty($entity_type_id) {
$entity_type_definition = $this->entityManager()->getDefinition($entity_type_id);
return [
'#theme' => 'item_list',
'#items' => [],
'#cache' => [
'tags' => $this->entityManager()->getDefinition($entity_type_id)->getListCacheTags(),
'contexts' => $entity_type_definition->getListCacheContexts(),
'tags' => $entity_type_definition->getListCacheTags(),
],
];
}
......
......@@ -33,6 +33,7 @@
* },
* base_table = "entity_test",
* persistent_cache = FALSE,
* list_cache_contexts = { "entity_test_view_grants" },
* entity_keys = {
* "id" = "id",
* "uuid" = "uuid",
......
......@@ -250,16 +250,6 @@ views_display:
rendering_language:
type: string
label: 'Entity language'
cache_metadata:
type: mapping
label: 'Cache metadata'
mapping:
cacheable:
type: boolean
label: 'Cacheable'
contexts:
type: sequence
label: 'Cache contexts'
exposed_block:
type: boolean
label: 'Put the exposed form in a block'
......
......@@ -114,6 +114,18 @@ views.view.*:
label: 'Position'
display_options:
type: views.display.[%parent.display_plugin]
cache_metadata:
type: mapping
label: 'Cache metadata'
mapping:
cacheable:
type: boolean
label: 'Cacheable'
contexts:
type: sequence
label: 'Cache contexts'
sequence:
type: string
views_block:
type: block_settings
......
......@@ -310,13 +310,14 @@ protected function addCacheMetadata() {
$current_display = $executable->current_display;
$displays = $this->get('display');
foreach ($displays as $display_id => $display) {
foreach (array_keys($displays) as $display_id) {
$display =& $this->getDisplay($display_id);
$executable->setDisplay($display_id);
list($display['cache_metadata']['cacheable'], $display['cache_metadata']['contexts']) = $executable->getDisplay()->calculateCacheMetadata();
// Always include at least the language context as there will be most
// probable translatable strings in the view output.
$display['cache_metadata']['contexts'][] = 'cache.context.language';
$display['cache_metadata']['contexts'][] = 'language';
$display['cache_metadata']['contexts'] = array_unique($display['cache_metadata']['contexts']);
}
// Restore the previous active display.
......
......@@ -137,6 +137,7 @@ public function getViewsData() {
$data[$views_base_table]['table']['base'] = [
'field' => $base_field,
'title' => $this->entityType->getLabel(),
'cache_contexts' => $this->entityType->getListCacheContexts(),
];
if ($label_key = $this->entityType->getKey('label')) {
......
......@@ -117,6 +117,17 @@ abstract class DisplayPluginBase extends PluginBase implements DisplayPluginInte
*/
protected static $unpackOptions = array();
/**
* The display information coming directly from the view entity.
*
* @see \Drupal\views\Entity\View::getDisplay()
*
* @todo \Drupal\views\Entity\View::duplicateDisplayAsType directly access it.
*
* @var array
*/
public $display;
/**
* Constructs a new DisplayPluginBase object.
*
......@@ -2302,6 +2313,9 @@ public function buildRenderable(array $args = []) {
'#arguments' => $args,
'#embed' => FALSE,
'#view' => $this->view,
'#cache' => [
'contexts' => isset($this->display['cache_metadata']['contexts']) ? $this->display['cache_metadata']['contexts'] : [],
],
];
}
......
......@@ -8,6 +8,7 @@
namespace Drupal\views\Plugin\views\query;
use Drupal\Core\Form\FormStateInterface;
use Drupal\views\Plugin\CacheablePluginInterface;
use Drupal\views\Plugin\views\PluginBase;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\ViewExecutable;
......@@ -35,7 +36,7 @@
/**
* Base plugin class for Views queries.
*/
abstract class QueryPluginBase extends PluginBase {
abstract class QueryPluginBase extends PluginBase implements CacheablePluginInterface {
/**
* A pager plugin that should be provided by the display.
......@@ -312,6 +313,27 @@ public function getEntityTableInfo() {
return $entity_tables;
}
/**
* {@inheritdoc}
*/
public function isCacheable() {
// This plugin can't really determine that.
return TRUE;
}
/**
* {@inheritdoc}
*/
public function getCacheContexts() {
$contexts = [];
if (($views_data = Views::viewsData()->get($this->view->storage->get('base_table'))) && !empty($views_data['table']['entity type'])) {
$entity_type_id = $views_data['table']['entity type'];
$entity_type = \Drupal::entityManager()->getDefinition($entity_type_id);
$contexts = $entity_type->getListCacheContexts();
}
return $contexts;
}
/**
* {@inheritdoc}
*/
......
......@@ -86,7 +86,7 @@ public function testGlossaryView() {
// Verify cache tags.
$this->enablePageCaching();
$this->assertPageCacheContextsAndTags(Url::fromRoute('view.glossary.page_1'), [], [
$this->assertPageCacheContextsAndTags(Url::fromRoute('view.glossary.page_1'), ['cache.context.url', 'node_view_grants', 'language'], [
'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',
......
......@@ -10,9 +10,10 @@
use Drupal\Core\Cache\Cache;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\views\Views;
use Drupal\views\Entity\View;
/**
* Tests the general integration between Views and the render cache.
* Tests the general integration between views and the render cache.
*
* @group views
*/
......@@ -23,12 +24,12 @@ class RenderCacheIntegrationTest extends ViewUnitTestBase {
/**
* {@inheritdoc}
*/
public static $testViews = ['entity_test_fields', 'entity_test_row'];
public static $testViews = ['test_view', 'test_display', 'entity_test_fields', 'entity_test_row'];
/**
* {@inheritdoc}
*/
public static $modules = ['entity_test', 'user'];
public static $modules = ['entity_test', 'user', 'node'];
/**
* {@inheritdoc}
......@@ -200,4 +201,27 @@ protected function assertCacheTagsForEntityBasedView($do_assert_views_caches) {
$this->assertViewsCacheTags($view, $result_tags_page_1, $do_assert_views_caches, $render_tags_page_1);
}
/**
* Ensure that the view renderable contains the cache contexts.
*/
public function testBuildRenderableWithCacheContexts() {
$view = View::load('test_view');
$display =& $view->getDisplay('default');
$display['cache_metadata']['contexts'] = ['beatles'];
$executable = $view->getExecutable();
$build = $executable->buildRenderable();
$this->assertEqual(['beatles'], $build['#cache']['contexts']);
}
/**
* Ensures that saving a view calculates the cache contexts.
*/
public function testViewAddCacheMetadata() {
$view = View::load('test_display');
$view->save();
$this->assertEqual(['node_view_grants', 'language'], $view->getDisplay('default')['cache_metadata']['contexts']);
}
}
......@@ -89,6 +89,7 @@ protected function setUp() {
'label' => 'Entity test',
'entity_keys' => ['id' => 'id', 'langcode' => 'langcode'],
'provider' => 'entity_test',
'list_cache_contexts' => ['entity_test_list_cache_context'],
]);
$this->translationManager = $this->getStringTranslationStub();
......@@ -164,6 +165,7 @@ public function testBaseTables() {
$this->assertEquals('entity_test', $data['entity_test']['table']['provider']);
$this->assertEquals('id', $data['entity_test']['table']['base']['field']);
$this->assertEquals(['entity_test_list_cache_context'], $data['entity_test']['table']['base']['cache_contexts']);
$this->assertEquals('Entity test', $data['entity_test']['table']['base']['title']);
$this->assertFalse(isset($data['entity_test']['table']['defaults']));
......@@ -173,7 +175,6 @@ public function testBaseTables() {
$this->assertFalse(isset($data['revision_data_table']));
}
/**
* Tests data_table support.
*/
......
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