Issue #2188565 by Wim Leers, Berdir: Test coverage for Comment, Custom Block, Node, Taxonomy Term and User entity cache tags.

8.0.x
Nathaniel Catchpole 2014-03-14 12:58:33 +00:00
parent e42ac27706
commit bd732ee75e
21 changed files with 1151 additions and 172 deletions

View File

@ -320,6 +320,9 @@ abstract class Entity implements EntityInterface {
*/
public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
$this->onSaveOrDelete();
if ($update) {
$this->onUpdateBundleEntity();
}
}
/**
@ -381,6 +384,21 @@ abstract class Entity implements EntityInterface {
}
}
/**
* Acts on entities of which this entity is a bundle entity type.
*/
protected function onUpdateBundleEntity() {
// If this entity is a bundle entity type of another entity type, and we're
// updating an existing entity, and that other entity type has a view
// builder class, then invalidate the render cache of entities for which
// this entity is a bundle.
$bundle_of = $this->getEntityType()->getBundleOf();
$entity_manager = \Drupal::entityManager();
if ($bundle_of !== FALSE && $entity_manager->hasController($bundle_of, 'view_builder')) {
$entity_manager->getViewBuilder($bundle_of)->resetCache();
}
}
/**
* Wraps the URL generator.
*

View File

@ -146,19 +146,21 @@ class EntityViewBuilder extends EntityControllerBase implements EntityController
"#{$this->entityTypeId}" => $entity,
'#view_mode' => $view_mode,
'#langcode' => $langcode,
'#cache' => array(
'tags' => array(
$this->entityTypeId . '_view' => TRUE,
$this->entityTypeId => array($entity->id()),
),
)
);
// Cache the rendered output if permitted by the view mode and global entity
// type configuration.
if ($this->isViewModeCacheable($view_mode) && !$entity->isNew() && $entity->isDefaultRevision() && $this->entityType->isRenderCacheable()) {
$return['#cache'] = array(
$return['#cache'] += array(
'keys' => array('entity_view', $this->entityTypeId, $entity->id(), $view_mode),
'granularity' => DRUPAL_CACHE_PER_ROLE,
'bin' => $this->cacheBin,
'tags' => array(
$this->entityTypeId . '_view' => TRUE,
$this->entityTypeId => array($entity->id()),
),
);
if ($entity instanceof TranslatableInterface && count($entity->getTranslationLanguages()) > 1) {

View File

@ -0,0 +1,44 @@
<?php
/**
* @file
* Contains \Drupal\custom_block\Tests\CustomBlockCacheTagsTest.
*/
namespace Drupal\custom_block\Tests;
use Drupal\system\Tests\Entity\EntityCacheTagsTestBase;
/**
* Tests the Custom Block entity's cache tags.
*/
class CustomBlockCacheTagsTest extends EntityCacheTagsTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('custom_block');
/**
* {@inheritdoc}
*/
public static function getInfo() {
return parent::generateStandardizedInfo('Custom Block', 'Custom Block');
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Llama" custom block.
$custom_block = entity_create('custom_block', array(
'info' => 'Llama',
'type' => 'basic',
'body' => 'The name "llama" was adopted by European settlers from native Peruvians.',
));
$custom_block->save();
return $custom_block;
}
}

View File

@ -0,0 +1,74 @@
<?php
/**
* @file
* Contains \Drupal\comment\Tests\CommentCacheTagsTest.
*/
namespace Drupal\comment\Tests;
use Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase;
/**
* Tests the Comment entity's cache tags.
*/
class CommentCacheTagsTest extends EntityWithUriCacheTagsTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('comment');
/**
* {@inheritdoc}
*/
public static function getInfo() {
return parent::generateStandardizedInfo('Comment', 'Comment');
}
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
// Give anonymous users permission to view comments, so that we can verify
// the cache tags of cached versions of comment pages.
$user_role = entity_load('user_role', DRUPAL_ANONYMOUS_RID);
$user_role->grantPermission('access comments');
$user_role->save();
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "bar" bundle for the "entity_test" entity type and create.
$bundle = 'bar';
entity_test_create_bundle($bundle, NULL, 'entity_test');
// Create a comment field on this bundle.
\Drupal::service('comment.manager')->addDefaultField('entity_test', 'bar');
// Create a "Camelids" test entity.
$entity_test = entity_create('entity_test', array(
'name' => 'Camelids',
'type' => 'bar',
));
$entity_test->save();
// Create a "Llama" taxonomy term.
$comment = entity_create('comment', array(
'subject' => 'Llama',
'comment_body' => 'The name "llama" was adopted by European settlers from native Peruvians.',
'entity_id' => $entity_test->id(),
'entity_type' => 'entity_test',
'field_name' => 'comment',
'status' => \Drupal\comment\CommentInterface::PUBLISHED,
));
$comment->save();
return $comment;
}
}

View File

@ -54,6 +54,7 @@ class CommentLockTest extends UnitTestCase {
unset($methods[array_search('preSave', $methods)]);
unset($methods[array_search('postSave', $methods)]);
$methods[] = 'onSaveOrDelete';
$methods[] = 'onUpdateBundleEntity';
$comment = $this->getMockBuilder('Drupal\comment\Entity\Comment')
->disableOriginalConstructor()
->setMethods($methods)

View File

@ -366,6 +366,15 @@ class FieldConfig extends ConfigEntityBase implements FieldConfigInterface {
public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
// Clear the cache.
field_cache_clear();
if ($update) {
// Invalidate the render cache for all affected entities.
$entity_manager = \Drupal::entityManager();
$entity_type = $this->getTargetEntityTypeId();
if ($entity_manager->hasController($entity_type, 'view_builder')) {
$entity_manager->getViewBuilder($entity_type)->resetCache();
}
}
}
/**

View File

@ -361,6 +361,13 @@ class FieldInstanceConfig extends ConfigEntityBase implements FieldInstanceConfi
public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
// Clear the cache.
field_cache_clear();
// Invalidate the render cache for all affected entities.
$entity_manager = \Drupal::entityManager();
$entity_type = $this->getTargetEntityTypeId();
if ($entity_manager->hasController($entity_type, 'view_builder')) {
$entity_manager->getViewBuilder($entity_type)->resetCache();
}
}
/**

View File

@ -0,0 +1,116 @@
<?php
/**
* @file
* Contains \Drupal\menu\Tests\MenuCacheTagsTest.
*/
namespace Drupal\menu\Tests;
use Drupal\system\Tests\Cache\PageCacheTagsTestBase;
/**
* Tests the Menu and Menu Link entities' cache tags.
*/
class MenuCacheTagsTest extends PageCacheTagsTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('menu', 'block', 'test_page_test');
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => "Menu & Menu link entities cache tags",
'description' => "Test the Menu & Menu link entities' cache tags.",
'group' => 'Menu',
);
}
/**
* Tests cache tags presence and invalidation of the Menu entity.
*
* Tests the following cache tags:
* - "menu:<menu ID>"
*/
public function testMenuBlock() {
$path = 'test-page';
// Create a Llama menu, add a link to it and place the corresponding block.
$menu = entity_create('menu', array(
'id' => 'llama',
'label' => 'Llama',
'description' => 'Description text',
));
$menu->save();
$menu_link = entity_create('menu_link', array(
'link_path' => '<front>',
'link_title' => 'Vicuña',
'menu_name' => 'llama',
));
$menu_link->save();
$block = $this->drupalPlaceBlock('system_menu_block:llama', array('label' => 'Llama', 'module' => 'system', 'region' => 'footer'));
// Prime the page cache.
$this->verifyPageCache($path, 'MISS');
// Verify a cache hit, but also the presence of the correct cache tags.
$this->verifyPageCache($path, 'HIT', array('content:1', 'menu:llama'));
// Verify that after modifying the menu, there is a cache miss.
$this->pass('Test modification of menu.', 'Debug');
$menu->label = 'Awesome llama';
$menu->save();
$this->verifyPageCache($path, 'MISS');
// Verify a cache hit.
$this->verifyPageCache($path, 'HIT');
// Verify that after modifying the menu link, there is a cache miss.
$this->pass('Test modification of menu link.', 'Debug');
$menu_link->link_title = 'Guanaco';
$menu_link->save();
$this->verifyPageCache($path, 'MISS');
// Verify a cache hit.
$this->verifyPageCache($path, 'HIT');
// Verify that after adding a menu link, there is a cache miss.
$this->pass('Test addition of menu link.', 'Debug');
$menu_link_2 = entity_create('menu_link', array(
'link_path' => '<front>',
'link_title' => 'Alpaca',
'menu_name' => 'llama',
));
$menu_link_2->save();
$this->verifyPageCache($path, 'MISS');
// Verify a cache hit.
$this->verifyPageCache($path, 'HIT');
// Verify that after deleting the first menu link, there is a cache miss.
$this->pass('Test deletion of menu link.', 'Debug');
$menu_link->delete();
$this->verifyPageCache($path, 'MISS');
// Verify a cache hit.
$this->verifyPageCache($path, 'HIT', array('content:1', 'menu:llama'));
// Verify that after deleting the menu, there is a cache miss.
$this->pass('Test deletion of menu.', 'Debug');
$menu->delete();
$this->verifyPageCache($path, 'MISS');
// Verify a cache hit.
$this->verifyPageCache($path, 'HIT', array('content:1'));
}
}

View File

@ -478,66 +478,6 @@ class MenuTest extends MenuWebTestBase {
$this->assertIdentical($json[$id], '<ul class="contextual-links"><li class="block-configure"><a href="' . base_path() . 'admin/structure/block/manage/' . $block->id() . '">Configure block</a></li><li class="menu-edit"><a href="' . base_path() . 'admin/structure/menu/manage/tools">Edit menu</a></li></ul>');
}
/**
* Test that cache tags are properly set and bubbled up to the page cache.
*
* Ensures that invalidation of the "menu:<menu name>" cache tags works.
*/
public function testMenuBlockPageCacheTags() {
// Enable page caching.
$config = \Drupal::config('system.performance');
$config->set('cache.page.use_internal', 1);
$config->set('cache.page.max_age', 300);
$config->save();
// Create a Llama menu, add a link to it and place the corresponding block.
$menu = entity_create('menu', array(
'id' => 'llama',
'label' => 'Llama',
'description' => 'Description text',
));
$menu->save();
$menu_link = entity_create('menu_link', array(
'link_path' => '<front>',
'link_title' => 'Vicuña',
'menu_name' => 'llama',
));
$menu_link->save();
$block = $this->drupalPlaceBlock('system_menu_block:llama', array('label' => 'Llama', 'module' => 'system', 'region' => 'footer'));
// Prime the page cache.
$this->drupalGet('test-page');
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
// Verify a cache hit, but also the presence of the correct cache tags.
$this->drupalGet('test-page');
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');
$cid_parts = array(url('test-page', array('absolute' => TRUE)), 'html');
$cid = sha1(implode(':', $cid_parts));
$cache_entry = \Drupal::cache('page')->get($cid);
$this->assertIdentical($cache_entry->tags, array('content:1', 'menu:llama'));
// The "Llama" menu is modified.
$menu->label = 'Awesome llama';
$menu->save();
// Verify that after the modified menu, there is a cache miss.
$this->drupalGet('test-page');
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
// Verify a cache hit.
$this->drupalGet('test-page');
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT');
// A link in the "Llama" menu is modified.
$menu_link->link_title = 'Guanaco';
$menu_link->save();
// Verify that after the modified menu link, there is a cache miss.
$this->drupalGet('test-page');
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS');
}
/**
* Tests menu link bundles.
*/

View File

@ -188,9 +188,6 @@ class NodeType extends ConfigEntityBase implements NodeTypeInterface {
else {
// Invalidate the cache tag of the updated node type only.
Cache::invalidateTags(array('node_type' => $this->id()));
// Invalidate the render cache for all nodes.
\Drupal::entityManager()->getViewBuilder('node')->resetCache();
}
}

View File

@ -0,0 +1,69 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodeCacheTagsTest.
*/
namespace Drupal\node\Tests;
use Drupal\Core\Entity\EntityInterface;
use Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase;
/**
* Tests the Node entity's cache tags.
*/
class NodeCacheTagsTest extends EntityWithUriCacheTagsTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('node');
/**
* {@inheritdoc}
*/
public static function getInfo() {
return parent::generateStandardizedInfo('Node', 'Node');
}
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
// Give anonymous users permission to view nodes, so that we can verify the
// cache tags of cached versions of node pages.
$user_role = entity_load('user_role', DRUPAL_ANONYMOUS_RID);
$user_role->grantPermission('acess content');
$user_role->save();
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Camelids" node type.
entity_create('node_type', array(
'name' => 'Camelids',
'type' => 'camelids',
))->save();
// Create a "Llama" node.
$node = entity_create('node', array('type' => 'camelids'));
$node->setTitle('Llama')
->setPublished(TRUE)
->save();
return $node;
}
/**
* {@inheritdoc}
*/
protected function getAdditionalCacheTagsForEntity(EntityInterface $node) {
return array('user:' . $node->getOwnerId());
}
}

View File

@ -1,98 +0,0 @@
<?php
/**
* @file
* Contains \Drupal\node\Tests\NodePageCacheTest.
*/
namespace Drupal\node\Tests;
/**
* Tests the cache invalidation of node operations.
*/
class NodePageCacheTest extends NodeTestBase {
/**
* An admin user with administrative permissions for nodes.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
public static $modules = array('views');
public static function getInfo() {
return array(
'name' => 'Node page cache test',
'description' => 'Test cache invalidation of node operations.',
'group' => 'Node',
);
}
function setUp() {
parent::setUp();
$this->container->get('config.factory')->get('system.performance')
->set('cache.page.use_internal', 1)
->set('cache.page.max_age', 300)
->save();
$this->adminUser = $this->drupalCreateUser(array(
'bypass node access',
'access content overview',
'administer nodes',
));
}
/**
* Tests deleting nodes clears page cache.
*/
public function testNodeDelete() {
$author = $this->drupalCreateUser();
$node_id = $this->drupalCreateNode(array('uid' => $author->id()))->id();
$node_path = 'node/' . $node_id;
// Populate page cache.
$this->drupalGet($node_path);
// Verify the presence of the correct cache tags.
$cid_parts = array(url($node_path, array('absolute' => TRUE)), 'html');
$cid = sha1(implode(':', $cid_parts));
$cache_entry = \Drupal::cache('page')->get($cid);
$this->assertIdentical($cache_entry->tags, array('content:1', 'node_view:' . $node_id, 'node:' . $node_id, 'user:' . $author->id(), 'filter_format:plain_text'));
// Login and delete the node.
$this->drupalLogin($this->adminUser);
$this->drupalGet($node_path . '/delete');
$this->drupalPostForm(NULL, array(), t('Delete'));
// Logout and check the node is not available.
$this->drupalLogout();
$this->drupalGet($node_path);
$this->assertResponse(404);
// Create two new nodes.
$this->drupalCreateNode();
$node_path = 'node/' . $this->drupalCreateNode()->id();
// Populate page cache.
$this->drupalGet($node_path);
// Login and delete the nodes.
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/content');
$edit = array(
'action' => 'node_delete_action',
'node_bulk_form[0]' => 1,
'node_bulk_form[1]' => 1,
);
$this->drupalPostForm(NULL, $edit, t('Apply'));
$this->drupalPostForm(NULL, array(), t('Delete'));
// Logout and check the node is not available.
$this->drupalLogout();
$this->drupalGet($node_path);
$this->assertResponse(404);
}
}

View File

@ -0,0 +1,56 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Cache\PageCacheTagsTestBase.
*/
namespace Drupal\system\Tests\Cache;
use Drupal\simpletest\WebTestBase;
use Drupal\Component\Utility\String;
/**
* Provides helper methods for page cache tags tests.
*/
abstract class PageCacheTagsTestBase extends WebTestBase {
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
// Enable page caching.
$config = \Drupal::config('system.performance');
$config->set('cache.page.use_internal', 1);
$config->set('cache.page.max_age', 3600);
$config->save();
}
/**
* Verify that when loading a given page, it's a page cache hit or miss.
*
* @param string $path
* The page at this path will be loaded.
* @param string $hit_or_miss
* 'HIT' if a page cache hit is expected, 'MISS' otherwise.
*
* @param array|FALSE $tags
* When expecting a page cache hit, you may optionally specify an array of
* expected cache tags. While FALSE, the cache tags will not be verified.
*/
protected function verifyPageCache($path, $hit_or_miss, $tags = FALSE) {
$this->drupalGet($path);
$message = String::format('Page cache @hit_or_miss for %path.', array('@hit_or_miss' => $hit_or_miss, '%path' => $path));
$this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), $hit_or_miss, $message);
if ($hit_or_miss === 'HIT' && is_array($tags)) {
$cid_parts = array(url($path, array('absolute' => TRUE)), 'html');
$cid = sha1(implode(':', $cid_parts));
$cache_entry = \Drupal::cache('page')->get($cid);
$this->assertIdentical($cache_entry->tags, $tags);
}
}
}

View File

@ -0,0 +1,419 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Entity\EntityCacheTagsTestBase.
*/
namespace Drupal\system\Tests\Entity;
use Drupal\Component\Utility\String;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\system\Tests\Cache\PageCacheTagsTestBase;
/**
* Provides helper methods for Entity cache tags tests.
*/
abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('entity_reference', 'entity_test', 'field_test');
/**
* The main entity used for testing.
*
* @var \Drupal\Core\Entity\EntityInterface
*/
protected $entity;
/**
* The entity instance referencing the main entity.
*
* @var \Drupal\Core\Entity\EntityInterface
*/
protected $referencing_entity;
/**
* The entity instance not referencing the main entity.
*
* @var \Drupal\Core\Entity\EntityInterface
*/
protected $non_referencing_entity;
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
// Give anonymous users permission to view test entities, so that we can
// verify the cache tags of cached versions of test entity pages.
$user_role = entity_load('user_role', DRUPAL_ANONYMOUS_RID);
$user_role->grantPermission('view test entity');
$user_role->save();
// Create an entity.
$this->entity = $this->createEntity();
// If this is a fieldable entity, then add a configurable field. We will use
// this configurable field in later tests to ensure that modifications to
// field (instance) configuration invalidate render cache entries.
if ($this->entity->getEntityType()->isFieldable()) {
// Add field, so we can modify the Field and FieldInstance entities to
// verify that changes to those indeed clear cache tags.
$field_name = drupal_strtolower($this->randomName());
entity_create('field_config', array(
'name' => 'configurable_field',
'entity_type' => $this->entity->getEntityTypeId(),
'type' => 'test_field',
'settings' => array(),
))->save();
entity_create('field_instance_config', array(
'entity_type' => $this->entity->getEntityTypeId(),
'bundle' => $this->entity->bundle(),
'field_name' => 'configurable_field',
'label' => 'Configurable field',
'settings' => array(),
))->save();
// Reload the entity now that a new field has been added to it.
$storage_controller = $this->container
->get('entity.manager')
->getStorageController($this->entity->getEntityTypeId());
$storage_controller->resetCache();
$this->entity = $storage_controller->load($this->entity->id());
}
// Create a referencing and a non-referencing entity.
list(
$this->referencing_entity,
$this->non_referencing_entity,
) = $this->createReferenceTestEntities($this->entity);
}
/**
* Generates standardized entity cache tags test info.
*
* @param string $entity_type_label
* The label of the entity type whose cache tags to test.
* @param string $group
* The test group.
*
* @return array
*
* @see \Drupal\simpletest\TestBase::getInfo()
*/
protected static function generateStandardizedInfo($entity_type_label, $group) {
return array(
'name' => "$entity_type_label entity cache tags",
'description' => "Test the $entity_type_label entity's cache tags.",
'group' => $group,
);
}
/**
* Creates the entity to be tested.
*
* @return \Drupal\Core\Entity\EntityInterface
* The entity to be tested.
*/
abstract protected function createEntity();
/**
* Creates a referencing and a non-referencing entity for testing purposes.
*
* @param \Drupal\Core\Entity\EntityInterface $referenced_entity
* The entity that the referencing entity should reference.
*
* @return \Drupal\Core\Entity\EntityInterface[]
* An array containing a referencing entity and a non-referencing entity.
*/
protected function createReferenceTestEntities($referenced_entity) {
// All referencing entities should be of the type 'entity_test'.
$entity_type = 'entity_test';
// Create a "foo" bundle for the given entity type.
$bundle = 'foo';
entity_test_create_bundle($bundle, NULL, $entity_type);
// Add a field of the given type to the given entity type's "foo" bundle.
$field_name = $referenced_entity->getEntityTypeId() . '_reference';
entity_create('field_config', array(
'name' => $field_name,
'entity_type' => $entity_type,
'type' => 'entity_reference',
'cardinality' => FieldDefinitionInterface::CARDINALITY_UNLIMITED,
'settings' => array(
'target_type' => $referenced_entity->getEntityTypeId(),
),
))->save();
entity_create('field_instance_config', array(
'field_name' => $field_name,
'entity_type' => $entity_type,
'bundle' => $bundle,
'settings' => array(
'handler' => 'default',
'handler_settings' => array(
'target_bundles' => array(
$referenced_entity->bundle() => $referenced_entity->bundle(),
),
'sort' => array('field' => '_none'),
'auto_create' => FALSE,
),
),
))->save();
entity_get_display($entity_type, $bundle, 'full')
->setComponent($field_name, array('type' => 'entity_reference_label'))
->save();
// Create an entity that does reference the entity being tested.
$label_key = \Drupal::entityManager()->getDefinition($entity_type)->getKey('label');
$referencing_entity = entity_create($entity_type, array(
$label_key => 'Referencing ' . $entity_type,
'status' => 1,
'type' => $bundle,
$field_name => array('target_id' => $referenced_entity->id()),
));
$referencing_entity->save();
// Create an entity that does not reference the entity being tested.
$non_referencing_entity = entity_create($entity_type, array(
$label_key => 'Non-referencing ' . $entity_type,
'status' => 1,
'type' => $bundle,
));
$non_referencing_entity->save();
return array(
$referencing_entity,
$non_referencing_entity,
);
}
/**
* Tests cache tags presence and invalidation of the entity when referenced.
*
* Tests the following cache tags:
* - "<entity type>_view:1"
* - "<entity type>:<entity ID>"
* - "<referencing entity type>_view:1"
* * - "<referencing entity type>:<referencing entity ID>"
*/
public function testReferencedEntity() {
$entity_type = $this->entity->getEntityTypeId();
$referencing_entity_path = $this->referencing_entity->getSystemPath();
$non_referencing_entity_path = $this->non_referencing_entity->getSystemPath();
$listing_path = 'entity_test/list/' . $entity_type . '_reference/' . $entity_type . '/' . $this->entity->id();
// Generate the standardized entity cache tags.
$cache_tag = $entity_type . ':' . $this->entity->id();
$view_cache_tag = $entity_type . '_view:1';
// Generate the cache tags for the (non) referencing entities.
$referencing_entity_cache_tags = array(
'entity_test_view:1',
'entity_test:' . $this->referencing_entity->id(),
// Includes the main entity's cache tags, since this entity references it.
$cache_tag,
$view_cache_tag
);
$non_referencing_entity_cache_tags = array(
'entity_test_view:1',
'entity_test:' . $this->non_referencing_entity->id(),
);
// Prime the page cache for the referencing entity.
$this->verifyPageCache($referencing_entity_path, 'MISS');
// Verify a cache hit, but also the presence of the correct cache tags.
$tags = array_merge(array('content:1'), $referencing_entity_cache_tags);
$this->verifyPageCache($referencing_entity_path, 'HIT', $tags);
// Also verify the existence of an entity render cache entry.
$cid = 'entity_view:entity_test:' . $this->referencing_entity->id() . ':full:stark:r.anonymous';
$cache_entry = \Drupal::cache()->get($cid);
$this->assertIdentical($cache_entry->tags, $referencing_entity_cache_tags);
// Prime the page cache for the non-referencing entity.
$this->verifyPageCache($non_referencing_entity_path, 'MISS');
// Verify a cache hit, but also the presence of the correct cache tags.
$tags = array_merge(array('content:1'), $non_referencing_entity_cache_tags);
$this->verifyPageCache($non_referencing_entity_path, 'HIT', $tags);
// Also verify the existence of an entity render cache entry.
$cid = 'entity_view:entity_test:' . $this->non_referencing_entity->id() . ':full:stark:r.anonymous';
$cache_entry = \Drupal::cache()->get($cid);
$this->assertIdentical($cache_entry->tags, $non_referencing_entity_cache_tags);
// Prime the page cache for the listing of referencing entities.
$this->verifyPageCache($listing_path, 'MISS');
// Verify a cache hit, but also the presence of the correct cache tags.
$tags = array_merge(array('content:1'), $referencing_entity_cache_tags);
$this->verifyPageCache($listing_path, 'HIT', $tags);
// Verify that after modifying the referenced entity, there is a cache miss
// for both the referencing entity, and the listing of referencing entities,
// but not for the non-referencing entity.
$this->pass("Test modification of referenced entity.", 'Debug');
$this->entity->save();
$this->verifyPageCache($referencing_entity_path, 'MISS');
$this->verifyPageCache($listing_path, 'MISS');
$this->verifyPageCache($non_referencing_entity_path, 'HIT');
// Verify cache hits.
$this->verifyPageCache($referencing_entity_path, 'HIT');
$this->verifyPageCache($listing_path, 'HIT');
// Verify that after modifying the referencing entity, there is a cache miss
// for both the referencing entity, and the listing of referencing entities,
// but not for the non-referencing entity.
$this->pass("Test modification of referencing entity.", 'Debug');
$this->referencing_entity->save();
$this->verifyPageCache($referencing_entity_path, 'MISS');
$this->verifyPageCache($listing_path, 'MISS');
$this->verifyPageCache($non_referencing_entity_path, 'HIT');
// Verify cache hits.
$this->verifyPageCache($referencing_entity_path, 'HIT');
$this->verifyPageCache($listing_path, 'HIT');
// Verify that after modifying the non-referencing entity, there is a cache
// miss for only the non-referencing entity, not for the referencing entity,
// nor for the listing of referencing entities.
$this->pass("Test modification of non-referencing entity.", 'Debug');
$this->non_referencing_entity->save();
$this->verifyPageCache($referencing_entity_path, 'HIT');
$this->verifyPageCache($listing_path, 'HIT');
$this->verifyPageCache($non_referencing_entity_path, 'MISS');
// Verify cache hits.
$this->verifyPageCache($non_referencing_entity_path, 'HIT');
// Verify that after modifying the entity's "full" display, there is a cache
// miss for both the referencing entity, and the listing of referencing
// entities, but not for the non-referencing entity.
$this->pass("Test modification of referenced entity's 'full' display.", 'Debug');
$entity_display = entity_get_display($entity_type, $this->entity->bundle(), 'full');
$entity_display->save();
$this->verifyPageCache($referencing_entity_path, 'MISS');
$this->verifyPageCache($listing_path, 'MISS');
$this->verifyPageCache($non_referencing_entity_path, 'HIT');
// Verify cache hits.
$this->verifyPageCache($referencing_entity_path, 'HIT');
$this->verifyPageCache($listing_path, 'HIT');
$bundle_entity_type = $this->entity->getEntityType()->getBundleEntityType();
if ($bundle_entity_type !== 'bundle') {
// Verify that after modifying the corresponding bundle entity, there is a
// cache miss for both the referencing entity, and the listing of
// referencing entities, but not for the non-referencing entity.
$this->pass("Test modification of referenced entity's bundle entity.", 'Debug');
$bundle_entity = entity_load($bundle_entity_type, $this->entity->bundle());
$bundle_entity->save();
$this->verifyPageCache($referencing_entity_path, 'MISS');
$this->verifyPageCache($listing_path, 'MISS');
$this->verifyPageCache($non_referencing_entity_path, 'HIT');
// Verify cache hits.
$this->verifyPageCache($referencing_entity_path, 'HIT');
$this->verifyPageCache($listing_path, 'HIT');
}
if ($this->entity->getEntityType()->isFieldable()) {
// Verify that after modifying a configurable field on the entity, there
// is a cache miss.
$this->pass("Test modification of referenced entity's configurable field.", 'Debug');
$field_name = $this->entity->getEntityTypeId() . '.configurable_field';
$field = entity_load('field_config', $field_name);
$field->save();
$this->verifyPageCache($referencing_entity_path, 'MISS');
$this->verifyPageCache($listing_path, 'MISS');
$this->verifyPageCache($non_referencing_entity_path, 'HIT');
// Verify cache hits.
$this->verifyPageCache($referencing_entity_path, 'HIT');
$this->verifyPageCache($listing_path, 'HIT');
// Verify that after modifying a configurable field instance on the
// entity, there is a cache miss.
$this->pass("Test modification of referenced entity's configurable field instance.", 'Debug');
$field_instance_name = $this->entity->getEntityTypeId() . '.' . $this->entity->bundle() . '.configurable_field';
$field_instance = entity_load('field_instance_config', $field_instance_name);
$field_instance->save();
$this->verifyPageCache($referencing_entity_path, 'MISS');
$this->verifyPageCache($listing_path, 'MISS');
$this->verifyPageCache($non_referencing_entity_path, 'HIT');
// Verify cache hits.
$this->verifyPageCache($referencing_entity_path, 'HIT');
$this->verifyPageCache($listing_path, 'HIT');
}
// Verify that after invalidating the entity's cache tag directly, there is
// a cache miss for both the referencing entity, and the listing of
// referencing entities, but not for the non-referencing entity.
$this->pass("Test invalidation of referenced entity's cache tag.", 'Debug');
Cache::invalidateTags(array($entity_type => array($this->entity->id())));
$this->verifyPageCache($referencing_entity_path, 'MISS');
$this->verifyPageCache($listing_path, 'MISS');
$this->verifyPageCache($non_referencing_entity_path, 'HIT');
// Verify cache hits.
$this->verifyPageCache($referencing_entity_path, 'HIT');
$this->verifyPageCache($listing_path, 'HIT');
// Verify that after invalidating the generic entity type's view cache tag
// directly, there is a cache miss for both the referencing entity, and the
// listing of referencing entities, but not for the non-referencing entity.
$this->pass("Test invalidation of referenced entity's 'view' cache tag.", 'Debug');
Cache::invalidateTags(array($entity_type . '_view' => TRUE));
$this->verifyPageCache($referencing_entity_path, 'MISS');
$this->verifyPageCache($listing_path, 'MISS');
$this->verifyPageCache($non_referencing_entity_path, 'HIT');
// Verify cache hits.
$this->verifyPageCache($referencing_entity_path, 'HIT');
$this->verifyPageCache($listing_path, 'HIT');
// Verify that after deleting the entity, there is a cache miss for both the
// referencing entity, and the listing of referencing entities, but not for
// the non-referencing entity.
$this->pass('Test deletion of referenced entity.', 'Debug');
$this->entity->delete();
$this->verifyPageCache($referencing_entity_path, 'MISS');
$this->verifyPageCache($listing_path, 'MISS');
$this->verifyPageCache($non_referencing_entity_path, 'HIT');
// Verify cache hits.
$tags = array(
'content:1',
'entity_test_view:1',
'entity_test:' . $this->referencing_entity->id(),
);
$this->verifyPageCache($referencing_entity_path, 'HIT', $tags);
$this->verifyPageCache($listing_path, 'HIT', array('content:1'));
}
}

View File

@ -48,7 +48,7 @@ class EntityViewBuilderTest extends EntityUnitTestBase {
// Test that new entities (before they are saved for the first time) do not
// generate a cache entry.
$build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
$this->assertFalse(isset($build['#cache']), 'The render array element of new (unsaved) entities is not cached.');
$this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == array('tags'), 'The render array element of new (unsaved) entities is not cached, but does have cache tags set.');
// Get a fully built entity view render array.
$entity_test->save();
@ -144,17 +144,17 @@ class EntityViewBuilderTest extends EntityUnitTestBase {
// Test a view mode in default conditions: render caching is enabled for
// the entity type and the view mode.
$build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'full');
$this->assertTrue(isset($build['#cache']), 'A view mode with render cache enabled has the correct output.');
$this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == array('tags', 'keys', 'granularity', 'bin') , 'A view mode with render cache enabled has the correct output (cache tags, keys, granularity and bin).');
// Test that a view mode can opt out of render caching.
$build = $this->container->get('entity.manager')->getViewBuilder('entity_test')->view($entity_test, 'test');
$this->assertFalse(isset($build['#cache']), 'A view mode with render cache disabled has the correct output.');
$this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == array('tags'), 'A view mode with render cache disabled has the correct output (only cache tags).');
// Test that an entity type can opt out of render caching completely.
$entity_test_no_cache = $this->createTestEntity('entity_test_label');
$entity_test_no_cache->save();
$build = $this->container->get('entity.manager')->getViewBuilder('entity_test_label')->view($entity_test_no_cache, 'full');
$this->assertFalse(isset($build['#cache']), 'An entity type can opt out of render caching regardless of view mode configuration.');
$this->assertTrue(isset($build['#cache']) && array_keys($build['#cache']) == array('tags'), 'An entity type can opt out of render caching regardless of view mode configuration, but always has cache tags set.');
}
/**

View File

@ -0,0 +1,152 @@
<?php
/**
* @file
* Contains \Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase.
*/
namespace Drupal\system\Tests\Entity;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityInterface;
use Drupal\system\Tests\Entity\EntityCacheTagsTestBase;
/**
* Provides helper methods for Entity cache tags tests; for entities with URIs.
*/
abstract class EntityWithUriCacheTagsTestBase extends EntityCacheTagsTestBase {
/**
* Returns the additional (non-standard) cache tags for the tested entity.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to be tested, as created by createEntity().
* @return array
* An array of the additional cache tags.
*
* @see \Drupal\system\Tests\Entity\EntityCacheTagsTestBase::createEntity()
*/
protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
return array();
}
/**
* Tests cache tags presence and invalidation of the entity at its URI.
*
* Tests the following cache tags:
* - "<entity type>_view:1"
* - "<entity_type>:<entity ID>"
*/
public function testEntityUri() {
$entity_path = $this->entity->getSystemPath();
$entity_type = $this->entity->getEntityTypeId();
// Generate the standardized entity cache tags.
$cache_tag = $entity_type . ':' . $this->entity->id();
$view_cache_tag = $entity_type . '_view:1';
// Prime the page cache.
$this->verifyPageCache($entity_path, 'MISS');
// Verify a cache hit, but also the presence of the correct cache tags.
$tags = array('content:1', $view_cache_tag, $cache_tag);
$this->verifyPageCache($entity_path, 'HIT');
// Also verify the existence of an entity render cache entry, if this entity
// type supports render caching.
if (\Drupal::entityManager()->getDefinition($entity_type)->isRenderCacheable()) {
$cid = 'entity_view:' . $entity_type . ':' . $this->entity->id() . ':full:stark:r.anonymous';
$cache_entry = \Drupal::cache()->get($cid);
$expected_cache_tags = array_merge(array($view_cache_tag, $cache_tag), $this->getAdditionalCacheTagsForEntity($this->entity));
$this->assertIdentical($cache_entry->tags, $expected_cache_tags);
}
// Verify that after modifying the entity, there is a cache miss.
$this->pass("Test modification of entity.", 'Debug');
$this->entity->save();
$this->verifyPageCache($entity_path, 'MISS');
// Verify a cache hit.
$this->verifyPageCache($entity_path, 'HIT');
// Verify that after modifying the entity's "full" display, there is a cache
// miss.
$this->pass("Test modification of entity's 'full' display.", 'Debug');
$entity_display = entity_get_display($entity_type, $this->entity->bundle(), 'full');
$entity_display->save();
$this->verifyPageCache($entity_path, 'MISS');
// Verify a cache hit.
$this->verifyPageCache($entity_path, 'HIT');
$bundle_entity_type = $this->entity->getEntityType()->getBundleEntityType();
if ($bundle_entity_type !== 'bundle') {
// Verify that after modifying the corresponding bundle entity, there is a
// cache miss.
$this->pass("Test modification of entity's bundle entity.", 'Debug');
$bundle_entity = entity_load($bundle_entity_type, $this->entity->bundle());
$bundle_entity->save();
$this->verifyPageCache($entity_path, 'MISS');
// Verify a cache hit.
$this->verifyPageCache($entity_path, 'HIT');
}
if ($this->entity->getEntityType()->isFieldable()) {
// Verify that after modifying a configurable field on the entity, there
// is a cache miss.
$this->pass("Test modification of entity's configurable field.", 'Debug');
$field_name = $this->entity->getEntityTypeId() . '.configurable_field';
$field = entity_load('field_config', $field_name);
$field->save();
$this->verifyPageCache($entity_path, 'MISS');
// Verify a cache hit.
$this->verifyPageCache($entity_path, 'HIT');
// Verify that after modifying a configurable field instance on the
// entity, there is a cache miss.
$this->pass("Test modification of entity's configurable field instance.", 'Debug');
$field_instance_name = $this->entity->getEntityTypeId() . '.' . $this->entity->bundle() . '.configurable_field';
$field_instance = entity_load('field_instance_config', $field_instance_name);
$field_instance->save();
$this->verifyPageCache($entity_path, 'MISS');
// Verify a cache hit.
$this->verifyPageCache($entity_path, 'HIT');
}
// Verify that after invalidating the entity's cache tag directly, there is
// a cache miss.
$this->pass("Test invalidation of entity's cache tag.", 'Debug');
Cache::invalidateTags(array($entity_type => array($this->entity->id())));
$this->verifyPageCache($entity_path, 'MISS');
// Verify a cache hit.
$this->verifyPageCache($entity_path, 'HIT');
// Verify that after invalidating the generic entity type's view cache tag
// directly, there is a cache miss.
$this->pass("Test invalidation of entity's 'view' cache tag.", 'Debug');
Cache::invalidateTags(array($entity_type . '_view' => TRUE));
$this->verifyPageCache($entity_path, 'MISS');
// Verify a cache hit.
$this->verifyPageCache($entity_path, 'HIT');
// Verify that after deleting the entity, there is a cache miss.
$this->pass('Test deletion of entity.', 'Debug');
$this->entity->delete();
$this->verifyPageCache($entity_path, 'MISS');
$this->assertResponse(404);
}
}

View File

@ -23,5 +23,13 @@ entity_test.render_no_view_mode:
requirements:
_access: 'TRUE'
entity_test.list_referencing_entities:
path: '/entity_test/list/{entity_reference_field_name}/{referenced_entity_type}/{referenced_entity_id}'
defaults:
_content: '\Drupal\entity_test\Controller\EntityTestController::listReferencingEntities'
_title: 'List entity_test entities referencing the given entity'
requirements:
_access: 'TRUE'
route_callbacks:
- '\Drupal\entity_test\Routing\EntityTestRoutes::routes'

View File

@ -8,6 +8,8 @@
namespace Drupal\entity_test\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\Query\QueryFactory;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
@ -15,6 +17,32 @@ use Symfony\Component\HttpFoundation\Request;
*/
class EntityTestController extends ControllerBase {
/**
* The entity query factory.
*
* @var \Drupal\Core\Entity\Query\QueryFactory
*/
protected $entityQueryFactory;
/**
* Constructs a new EntityTestController.
*
* @param \Drupal\Core\Entity\Query\QueryFactory
* The entity query factory.
*/
public function __construct(QueryFactory $entity_query_factory) {
$this->entityQueryFactory = $entity_query_factory;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('entity.query')
);
}
/**
* Displays the 'Add new entity_test' form.
*
@ -60,4 +88,37 @@ class EntityTestController extends ControllerBase {
return '';
}
/**
* List entity_test entities referencing the given entity.
*
* @param string $entity_reference_field_name
* The name of the entity_reference field to use in the query.
* @param string $referenced_entity_type
* The type of the entity being referenced.
* @param int $referenced_entity_id
* The ID of the entity being referenced.
*
* @return array
* A renderable array.
*/
public function listReferencingEntities($entity_reference_field_name, $referenced_entity_type, $referenced_entity_id) {
// Early return if the referenced entity does not exist (or is deleted).
$referenced_entity = $this->entityManager()
->getStorageController($referenced_entity_type)
->load($referenced_entity_id);
if ($referenced_entity === NULL) {
return array();
}
$query = $this->entityQueryFactory
->get('entity_test')
->condition($entity_reference_field_name . '.target_id', $referenced_entity_id);
$entities = $this->entityManager()
->getStorageController('entity_test')
->loadMultiple($query->execute());
return $this->entityManager()
->getViewBuilder('entity_test')
->viewMultiple($entities, 'full');
}
}

View File

@ -0,0 +1,50 @@
<?php
/**
* @file
* Contains \Drupal\taxonomy\Tests\TermCacheTagsTest.
*/
namespace Drupal\taxonomy\Tests;
use Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase;
/**
* Tests the Taxonomy term entity's cache tags.
*/
class TermCacheTagsTest extends EntityWithUriCacheTagsTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('taxonomy');
/**
* {@inheritdoc}
*/
public static function getInfo() {
return parent::generateStandardizedInfo('Taxonomy term', 'Taxonomy');
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Camelids" vocabulary.
$vocabulary = entity_create('taxonomy_vocabulary', array(
'name' => 'Camelids',
'vid' => 'camelids',
));
$vocabulary->save();
// Create a "Llama" taxonomy term.
$term = entity_create('taxonomy_term', array(
'name' => 'Llama',
'vid' => $vocabulary->id(),
));
$term->save();
return $term;
}
}

View File

@ -16,12 +16,11 @@ use Drupal\Core\Config\Entity\ConfigStorageController;
class VocabularyStorageController extends ConfigStorageController implements VocabularyStorageControllerInterface {
/**
* Overrides Drupal\Core\Config\Entity\ConfigStorageController::resetCache().
* {@inheritdoc}
*/
public function resetCache(array $ids = NULL) {
drupal_static_reset('taxonomy_vocabulary_get_names');
parent::resetCache($ids);
Cache::invalidateTags(array('content' => TRUE));
entity_info_cache_clear();
}

View File

@ -0,0 +1,55 @@
<?php
/**
* @file
* Contains \Drupal\user\Tests\UserCacheTagsTest.
*/
namespace Drupal\user\Tests;
use Drupal\system\Tests\Entity\EntityWithUriCacheTagsTestBase;
/**
* Tests the User entity's cache tags.
*/
class UserCacheTagsTest extends EntityWithUriCacheTagsTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('user');
/**
* {@inheritdoc}
*/
public static function getInfo() {
return parent::generateStandardizedInfo('User', 'User');
}
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
// Give anonymous users permission to view user profiles, so that we can
// verify the cache tags of cached versions of user profile pages.
$user_role = entity_load('user_role', DRUPAL_ANONYMOUS_RID);
$user_role->grantPermission('access user profiles');
$user_role->save();
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Llama" user.
$user = entity_create('user', array(
'name' => 'Llama',
));
$user->save();
return $user;
}
}