.../Drupal/Core/Config/Entity/ConfigEntityBase.php | 3 + core/lib/Drupal/Core/Entity/Entity.php | 65 ++++++++++++++-- core/lib/Drupal/Core/Entity/EntityInterface.php | 19 +++++ core/lib/Drupal/Core/Entity/EntityViewBuilder.php | 23 +++--- .../Core/Entity/EntityViewBuilderInterface.php | 11 +++ .../block/lib/Drupal/block/BlockViewBuilder.php | 27 ++----- .../comment/Tests/Entity/CommentLockTest.php | 9 ++- core/modules/filter/filter.module | 1 - .../lib/Drupal/filter/Entity/FilterFormat.php | 8 +- .../node/lib/Drupal/node/Entity/NodeType.php | 11 --- .../system/lib/Drupal/system/Entity/Menu.php | 19 ----- .../Tests/Entity/EntityCacheTagsTestBase.php | 2 +- core/modules/user/lib/Drupal/user/Entity/Role.php | 13 ---- .../user/lib/Drupal/user/PermissionsHash.php | 2 +- .../modules/views/lib/Drupal/views/Entity/View.php | 14 +--- .../views_ui/lib/Drupal/views_ui/ViewUI.php | 14 ++++ .../Config/Entity/ConfigEntityBaseUnitTest.php | 16 ++++ .../Core/Config/Entity/ConfigEntityStorageTest.php | 78 +++++++++++++++++++- .../Drupal/Tests/Core/Entity/EntityUnitTest.php | 42 ++++++++++- 19 files changed, 268 insertions(+), 109 deletions(-) diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php index dd6481e..42df6cd 100644 --- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php +++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityBase.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Config\Entity; use Drupal\Component\Utility\String; +use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\Entity; use Drupal\Core\Config\ConfigDuplicateUUIDException; use Drupal\Core\Entity\EntityStorageInterface; @@ -163,6 +164,8 @@ public function enable() { * {@inheritdoc} */ public function disable() { + // An entity was disabled, invalidate its own cache tag. + Cache::invalidateTags(array($this->entityTypeId => array($this->id()))); return $this->setStatus(FALSE); } diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php index 2300f60..ed95b23 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -7,7 +7,9 @@ namespace Drupal\Core\Entity; +use Drupal\Core\Cache\Cache; use Drupal\Core\DependencyInjection\DependencySerialization; +use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\String; use Drupal\Component\Utility\Unicode; use Drupal\Core\Config\Entity\Exception\ConfigEntityIdLengthException; @@ -357,9 +359,7 @@ public function preSave(EntityStorageInterface $storage) { */ public function postSave(EntityStorageInterface $storage, $update = TRUE) { $this->onSaveOrDelete(); - if ($update) { - $this->onUpdateBundleEntity(); - } + $this->invalidateTagsOnSave($update); } /** @@ -384,9 +384,7 @@ public static function preDelete(EntityStorageInterface $storage, array $entitie * {@inheritdoc} */ public static function postDelete(EntityStorageInterface $storage, array $entities) { - foreach ($entities as $entity) { - $entity->onSaveOrDelete(); - } + self::invalidateTagsOnDelete($entities); } /** @@ -403,6 +401,21 @@ public function referencedEntities() { } /** + * {@inheritdoc} + */ + public function getCacheTag() { + return array($this->entityTypeId => array($this->id())); + } + + /** + * {@inheritdoc} + */ + public function getListCacheTags() { + // @todo Add bundle-specific listing cache tag? https://drupal.org/node/2145751 + return array($this->entityTypeId . 's' => TRUE); + } + + /** * Acts on an entity after it was saved or deleted. */ protected function onSaveOrDelete() { @@ -422,6 +435,46 @@ protected function onSaveOrDelete() { } /** + * Invalidates an entity's cache tags upon save. + * + * @param bool $update + * TRUE if the entity has been updated, or FALSE if it has been inserted. + */ + protected function invalidateTagsOnSave($update) { + // An entity was created or updated: invalidate its list cache tags. (An + // updated entity may start to appear in a listing because it now meets that + // listing's filtering requirements. A newly created entity may start to + // appear in listings because it did not exist before.) + $tags = $this->getListCacheTags(); + if ($update) { + // An existing entity was updated, also invalidate its unique cache tag. + $tags = NestedArray::mergeDeep($tags, $this->getCacheTag()); + $this->onUpdateBundleEntity(); + } + Cache::invalidateTags($tags); + } + + /** + * Invalidates an entity's cache tags upon delete. + * + * @param \Drupal\Core\Entity\EntityInterface[] $entities + * An array of entities. + */ + protected static function invalidateTagsOnDelete(array $entities) { + $tags = array(); + foreach ($entities as $entity) { + // An entity was deleted: invalidate its own cache tag, but also its list + // cache tags. (A deleted entity may cause changes in a paged list on + // other pages than the one it's on. The one it's on is handled by its own + // cache tag, but subsequent list pages would not be invalidated, hence we + // must invalidate its list cache tags as well.) + $tags = NestedArray::mergeDeepArray(array($tags, $entity->getCacheTag(), $entity->getListCacheTags())); + $entity->onSaveOrDelete(); + } + Cache::invalidateTags($tags); + } + + /** * Acts on entities of which this entity is a bundle entity type. */ protected function onUpdateBundleEntity() { diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php index 90e4699..ab62144 100644 --- a/core/lib/Drupal/Core/Entity/EntityInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityInterface.php @@ -324,4 +324,23 @@ public function setOriginalId($id); */ public function toArray(); + /** + * The unique cache tag associated with this entity. + * + * @return array + * An array of cache tags. + */ + public function getCacheTag(); + + /** + * The list cache tags associated with this entity. + * + * Enables code listing entities of this type to ensure that newly created + * entities show up immediately. + * + * @return array + * An array of cache tags. + */ + public function getListCacheTags(); + } diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php index b4c72a4..a0b8154 100644 --- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php +++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php @@ -7,6 +7,7 @@ namespace Drupal\Core\Entity; +use Drupal\Component\Utility\NestedArray; use Drupal\Core\Cache\Cache; use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Field\FieldItemInterface; @@ -144,11 +145,8 @@ protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langco '#view_mode' => $view_mode, '#langcode' => $langcode, '#cache' => array( - 'tags' => array( - $this->entityTypeId . '_view' => TRUE, - $this->entityTypeId => array($entity->id()), - ), - ) + 'tags' => NestedArray::mergeDeep($this->getCacheTag(), $entity->getCacheTag()), + ), ); // Cache the rendered output if permitted by the view mode and global entity @@ -274,14 +272,12 @@ public function resetCache(array $entities = NULL) { // Always invalidate the ENTITY_TYPE_list tag. $tags = array($this->entityTypeId . '_list' => TRUE); foreach ($entities as $entity) { - $id = $entity->id(); - $tags[$this->entityTypeId][$id] = $id; - $tags[$this->entityTypeId . '_view_' . $entity->bundle()] = TRUE; + $tags = NestedArray::mergeDeep($tags, $entity->getCacheTag()); } - Cache::deleteTags($tags); + Cache::invalidateTags($tags); } else { - Cache::deleteTags(array($this->entityTypeId . '_view' => TRUE)); + Cache::invalidateTags($this->getCacheTag()); } } @@ -364,4 +360,11 @@ protected function isViewModeCacheable($view_mode) { return !empty($view_modes_info[$view_mode]['cache']); } + /** + * {@inheritdoc} + */ + public function getCacheTag() { + return array($this->entityTypeId . '_view' => TRUE); + } + } diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php b/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php index b41848b..aee9588 100644 --- a/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityViewBuilderInterface.php @@ -149,4 +149,15 @@ public function viewField(FieldItemListInterface $items, $display_options = arra */ public function viewFieldItem(FieldItemInterface $item, $display_options = array()); + /** + * The cache tag associated with this entity view builder. + * + * An entity view builder is instantiated on a per-entity type basis, so the + * cache tags are also per-entity type. + * + * @return array + * An array of cache tags. + */ + public function getCacheTag(); + } diff --git a/core/modules/block/lib/Drupal/block/BlockViewBuilder.php b/core/modules/block/lib/Drupal/block/BlockViewBuilder.php index a6a925a..85bbbcf 100644 --- a/core/modules/block/lib/Drupal/block/BlockViewBuilder.php +++ b/core/modules/block/lib/Drupal/block/BlockViewBuilder.php @@ -68,15 +68,12 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la ); $build[$entity_id]['#configuration']['label'] = check_plain($configuration['label']); - // Set cache tags; these always need to be set, whether the block is - // cacheable or not, so that the page cache is correctly informed. - $default_cache_tags = array( - 'content' => TRUE, - 'block_view' => TRUE, - 'block' => array($entity->id()), - ); - $build[$entity_id]['#cache']['tags'] = NestedArray::mergeDeep($default_cache_tags, $plugin->getCacheTags()); - + $build[$entity_id]['#cache']['tags'] = NestedArray::mergeDeepArray(array( + array('content' => TRUE), + $this->getCacheTag(), // Block view builder cache tag. + $entity->getCacheTag(), // Block entity cache tag. + $plugin->getCacheTags(), // Block plugin cache tags. + )); if ($plugin->isCacheable()) { $build[$entity_id]['#pre_render'][] = array($this, 'buildBlock'); @@ -155,16 +152,4 @@ public function buildBlock($build) { return $build; } - /** - * {@inheritdoc} - */ - public function resetCache(array $entities = NULL) { - if (isset($entities)) { - Cache::invalidateTags(array('block' => array_keys($entities))); - } - else { - Cache::invalidateTags(array('block_view' => TRUE)); - } - } - } diff --git a/core/modules/comment/tests/Drupal/comment/Tests/Entity/CommentLockTest.php b/core/modules/comment/tests/Drupal/comment/Tests/Entity/CommentLockTest.php index 2101d93..887d3ca 100644 --- a/core/modules/comment/tests/Drupal/comment/Tests/Entity/CommentLockTest.php +++ b/core/modules/comment/tests/Drupal/comment/Tests/Entity/CommentLockTest.php @@ -36,6 +36,8 @@ public function testLocks() { $container = new ContainerBuilder(); $container->set('module_handler', $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface')); $container->set('current_user', $this->getMock('Drupal\Core\Session\AccountInterface')); + $container->set('cache.test', $this->getMock('Drupal\Core\Cache\CacheBackendInterface')); + $container->setParameter('cache_bins', array('cache.test' => 'test')); $container->register('request', 'Symfony\Component\HttpFoundation\Request'); $lock = $this->getMock('Drupal\Core\Lock\LockBackendInterface'); $cid = 2; @@ -84,7 +86,12 @@ public function testLocks() { ->method('get') ->with('status') ->will($this->returnValue((object) array('value' => NULL))); - + $comment->expects($this->once()) + ->method('getCacheTag') + ->will($this->returnValue(array('comment' => array($cid)))); + $comment->expects($this->once()) + ->method('getListCacheTags') + ->will($this->returnValue(array('comments' => TRUE))); $storage = $this->getMock('Drupal\comment\CommentStorageInterface'); $comment->preSave($storage); $comment->postSave($storage); diff --git a/core/modules/filter/filter.module b/core/modules/filter/filter.module index 73aec62..09f9e83 100644 --- a/core/modules/filter/filter.module +++ b/core/modules/filter/filter.module @@ -176,7 +176,6 @@ function filter_formats(AccountInterface $account = NULL) { * @see filter_formats() */ function filter_formats_reset() { - Cache::deleteTags(array('filter_formats' => TRUE)); drupal_static_reset('filter_formats'); } diff --git a/core/modules/filter/lib/Drupal/filter/Entity/FilterFormat.php b/core/modules/filter/lib/Drupal/filter/Entity/FilterFormat.php index 365a8aa..9974a63 100644 --- a/core/modules/filter/lib/Drupal/filter/Entity/FilterFormat.php +++ b/core/modules/filter/lib/Drupal/filter/Entity/FilterFormat.php @@ -7,7 +7,6 @@ namespace Drupal\filter\Entity; -use Drupal\Core\Cache\Cache; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Config\Entity\EntityWithPluginBagInterface; use Drupal\Core\Entity\EntityStorageInterface; @@ -197,7 +196,6 @@ public function disable() { // Clear the filter cache whenever a text format is disabled. filter_formats_reset(); - Cache::deleteTags(array('filter_format' => $this->format)); return $this; } @@ -235,11 +233,7 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { // Clear the static caches of filter_formats() and others. filter_formats_reset(); - if ($update) { - // Clear the filter cache whenever a text format is updated. - Cache::deleteTags(array('filter_format' => $this->id())); - } - else { + if (!$update) { // Default configuration of modules and installation profiles is allowed // to specify a list of user roles to grant access to for the new format; // apply the defined user role permissions when a new format is inserted diff --git a/core/modules/node/lib/Drupal/node/Entity/NodeType.php b/core/modules/node/lib/Drupal/node/Entity/NodeType.php index 8ca6f72..fde17a2 100644 --- a/core/modules/node/lib/Drupal/node/Entity/NodeType.php +++ b/core/modules/node/lib/Drupal/node/Entity/NodeType.php @@ -8,7 +8,6 @@ namespace Drupal\node\Entity; use Drupal\Component\Utility\NestedArray; -use Drupal\Core\Cache\Cache; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\node\NodeTypeInterface; @@ -157,9 +156,6 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { parent::postSave($storage, $update); if (!$update) { - // Clear the node type cache, so the new type appears. - Cache::deleteTags(array('node_types' => TRUE)); - entity_invoke_bundle_hook('create', 'node', $this->id()); // Create a body if the create_body property is true and we're not in @@ -170,9 +166,6 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { } } elseif ($this->getOriginalId() != $this->id()) { - // Clear the node type cache to reflect the rename. - Cache::deleteTags(array('node_types' => TRUE)); - $update_count = node_type_update_nodes($this->getOriginalId(), $this->id()); if ($update_count) { drupal_set_message(format_plural($update_count, @@ -185,10 +178,6 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { } entity_invoke_bundle_hook('rename', 'node', $this->getOriginalId(), $this->id()); } - else { - // Invalidate the cache tag of the updated node type only. - Cache::invalidateTags(array('node_type' => $this->id())); - } } /** diff --git a/core/modules/system/lib/Drupal/system/Entity/Menu.php b/core/modules/system/lib/Drupal/system/Entity/Menu.php index 4b6cee5..2ea8dd1 100644 --- a/core/modules/system/lib/Drupal/system/Entity/Menu.php +++ b/core/modules/system/lib/Drupal/system/Entity/Menu.php @@ -7,7 +7,6 @@ namespace Drupal\system\Entity; -use Drupal\Core\Cache\Cache; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\system\MenuInterface; @@ -81,22 +80,4 @@ public function isLocked() { return (bool) $this->locked; } - /** - * {@inheritdoc} - */ - public function postSave(EntityStorageInterface $storage, $update = TRUE) { - parent::postSave($storage, $update); - - Cache::invalidateTags(array('menu' => $this->id())); - } - - /** - * {@inheritdoc} - */ - public static function postDelete(EntityStorageInterface $storage, array $entities) { - parent::postDelete($storage, $entities); - - Cache::invalidateTags(array('menu' => array_keys($entities))); - } - } diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php index e1f66b5..3b7b640 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityCacheTagsTestBase.php @@ -220,7 +220,7 @@ public function testReferencedEntity() { 'entity_test:' . $this->referencing_entity->id(), // Includes the main entity's cache tags, since this entity references it. $cache_tag, - $view_cache_tag + $view_cache_tag, ); $non_referencing_entity_cache_tags = array( 'entity_test_view:1', diff --git a/core/modules/user/lib/Drupal/user/Entity/Role.php b/core/modules/user/lib/Drupal/user/Entity/Role.php index 81a0ee6..d9cd07c 100644 --- a/core/modules/user/lib/Drupal/user/Entity/Role.php +++ b/core/modules/user/lib/Drupal/user/Entity/Role.php @@ -7,7 +7,6 @@ namespace Drupal\user\Entity; -use Drupal\Core\Cache\Cache; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\user\RoleInterface; @@ -134,20 +133,8 @@ public function preSave(EntityStorageInterface $storage) { public function postSave(EntityStorageInterface $storage, $update = TRUE) { parent::postSave($storage, $update); - Cache::invalidateTags(array('role' => $this->id())); // Clear render cache. entity_render_cache_clear(); } - /** - * {@inheritdoc} - */ - public static function postDelete(EntityStorageInterface $storage, array $entities) { - parent::postDelete($storage, $entities); - - $ids = array_keys($entities); - $storage->deleteRoleReferences($ids); - Cache::invalidateTags(array('role' => $ids)); - } - } diff --git a/core/modules/user/lib/Drupal/user/PermissionsHash.php b/core/modules/user/lib/Drupal/user/PermissionsHash.php index 3c6fe87..1fed13e 100644 --- a/core/modules/user/lib/Drupal/user/PermissionsHash.php +++ b/core/modules/user/lib/Drupal/user/PermissionsHash.php @@ -58,7 +58,7 @@ public function generate(AccountInterface $account) { } else { $permissions_hash = $this->doGenerate($sorted_roles); - $this->cache->set("user_permissions_hash:$role_list", $permissions_hash, Cache::PERMANENT, array('role' => $sorted_roles)); + $this->cache->set("user_permissions_hash:$role_list", $permissions_hash, Cache::PERMANENT, array('user_role' => $sorted_roles)); } return $permissions_hash; diff --git a/core/modules/views/lib/Drupal/views/Entity/View.php b/core/modules/views/lib/Drupal/views/Entity/View.php index 2beb7ac..304d1f8 100644 --- a/core/modules/views/lib/Drupal/views/Entity/View.php +++ b/core/modules/views/lib/Drupal/views/Entity/View.php @@ -7,7 +7,6 @@ namespace Drupal\views\Entity; -use Drupal\Core\Cache\Cache; use Drupal\Core\Config\Entity\ConfigEntityBase; use Drupal\Core\Entity\EntityStorageInterface; use Drupal\views\Views; @@ -305,10 +304,7 @@ public function calculateDependencies() { public function postSave(EntityStorageInterface $storage, $update = TRUE) { parent::postSave($storage, $update); - // Clear cache tags for this view. // @todo Remove if views implements a view_builder controller. - $id = $this->id(); - Cache::deleteTags(array('view' => array($id => $id))); views_invalidate_cache(); } @@ -359,17 +355,9 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti parent::postDelete($storage, $entities); $tempstore = \Drupal::service('user.tempstore')->get('views'); - $tags = array(); - foreach ($entities as $entity) { - $id = $entity->id(); - $tempstore->delete($id); - $tags['view'][$id] = $id; + $tempstore->delete($entity->id()); } - - // Clear cache tags for these views. - // @todo Remove if views implements a view_builder controller. - Cache::deleteTags($tags); } /** diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php index 3d49837..b7b0ab9 100644 --- a/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php +++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php @@ -1212,4 +1212,18 @@ public function calculateDependencies() { public function getConfigDependencyName() { } + /** + * {@inheritdoc} + */ + public function getCacheTag() { + $this->storage->getCacheTag(); + } + + /** + * {@inheritdoc} + */ + public function getListCacheTags() { + $this->storage->getListCacheTags(); + } + } diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php index a23f032..583f838 100644 --- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityBaseUnitTest.php @@ -78,6 +78,13 @@ class ConfigEntityBaseUnitTest extends UnitTestCase { protected $id; /** + * The mocked cache backend. + * + * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $cacheBackend; + + /** * {@inheritdoc} */ public static function getInfo() { @@ -119,10 +126,14 @@ public function setUp() { ->with('en') ->will($this->returnValue(new Language(array('id' => 'en')))); + $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); + $container = new ContainerBuilder(); $container->set('entity.manager', $this->entityManager); $container->set('uuid', $this->uuid); $container->set('language_manager', $this->languageManager); + $container->set('cache.test', $this->cacheBackend); + $container->setParameter('cache_bins', array('cache.test' => 'test')); \Drupal::setContainer($container); $this->entity = $this->getMockForAbstractClass('\Drupal\Core\Config\Entity\ConfigEntityBase', array($values, $this->entityTypeId)); @@ -325,6 +336,10 @@ public function testEnable() { * @depends testSetStatus */ public function testDisable() { + $this->cacheBackend->expects($this->once()) + ->method('invalidateTags') + ->with(array($this->entityTypeId => array($this->id))); + $this->entity->setStatus(TRUE); $this->assertSame($this->entity, $this->entity->disable()); $this->assertFalse($this->entity->status()); @@ -417,6 +432,7 @@ public function testToArray() { $this->assertSame($this->entity->get($name), $properties[$name]); } } + } class TestConfigurablePlugin extends PluginBase implements ConfigurablePluginInterface { diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php index d6f5c9c..fb1ab1c 100644 --- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php @@ -28,6 +28,13 @@ class ConfigEntityStorageTest extends UnitTestCase { protected $entityType; /** + * The type ID of the entity under test. + * + * @var string + */ + protected $entityTypeId; + + /** * The module handler. * * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject @@ -84,6 +91,13 @@ class ConfigEntityStorageTest extends UnitTestCase { protected $entityManager; /** + * The mocked cache backend. + * + * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $cacheBackend; + + /** * {@inheritdoc} */ public static function getInfo() { @@ -101,6 +115,7 @@ protected function setUp() { parent::setUp(); $this->entityType = $this->getMock('Drupal\Core\Entity\EntityTypeInterface'); + $this->entityTypeId = 'test_entity_type'; $this->entityType->expects($this->any()) ->method('getKey') ->will($this->returnValueMap(array( @@ -109,7 +124,7 @@ protected function setUp() { ))); $this->entityType->expects($this->any()) ->method('id') - ->will($this->returnValue('test_entity_type')); + ->will($this->returnValue($this->entityTypeId)); $this->entityType->expects($this->any()) ->method('getConfigPrefix') ->will($this->returnValue('the_config_prefix')); @@ -144,8 +159,12 @@ protected function setUp() { ->with('test_entity_type') ->will($this->returnValue($this->entityType)); + $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); + $container = new ContainerBuilder(); $container->set('entity.manager', $this->entityManager); + $container->set('cache.test', $this->cacheBackend); + $container->setParameter('cache_bins', array('cache.test' => 'test')); \Drupal::setContainer($container); } @@ -158,6 +177,9 @@ public function testCreateWithPredefinedUuid() { ->method('getClass') ->will($this->returnValue(get_class($this->getMockEntity()))); + $this->cacheBackend->expects($this->never()) + ->method('invalidateTags'); + $this->moduleHandler->expects($this->at(0)) ->method('invokeAll') ->with('test_entity_type_create'); @@ -183,6 +205,9 @@ public function testCreate() { ->method('getClass') ->will($this->returnValue(get_class($this->getMockEntity()))); + $this->cacheBackend->expects($this->never()) + ->method('invalidateTags'); + $this->moduleHandler->expects($this->at(0)) ->method('invokeAll') ->with('test_entity_type_create'); @@ -221,6 +246,12 @@ public function testSaveInsert(EntityInterface $entity) { $config_object->expects($this->once()) ->method('save'); + $this->cacheBackend->expects($this->once()) + ->method('invalidateTags') + ->with(array( + $this->entityTypeId . 's' => TRUE, // List cache tag. + )); + $this->configFactory->expects($this->once()) ->method('get') ->with('the_config_prefix.foo') @@ -273,6 +304,13 @@ public function testSaveUpdate(EntityInterface $entity) { $config_object->expects($this->once()) ->method('save'); + $this->cacheBackend->expects($this->once()) + ->method('invalidateTags') + ->with(array( + $this->entityTypeId . 's' => TRUE, // List cache tag. + $this->entityTypeId => array('foo'), // Own cache tag. + )); + $this->configFactory->expects($this->exactly(2)) ->method('loadMultiple') ->with(array('the_config_prefix.foo')) @@ -325,6 +363,13 @@ public function testSaveRename(ConfigEntityInterface $entity) { $config_object->expects($this->once()) ->method('save'); + $this->cacheBackend->expects($this->once()) + ->method('invalidateTags') + ->with(array( + $this->entityTypeId . 's' => TRUE, // List cache tag. + $this->entityTypeId => array('bar'), // Own cache tag. + )); + $this->configFactory->expects($this->once()) ->method('rename') ->will($this->returnValue($config_object)); @@ -362,6 +407,9 @@ public function testSaveRename(ConfigEntityInterface $entity) { * @expectedExceptionMessage The entity does not have an ID. */ public function testSaveInvalid() { + $this->cacheBackend->expects($this->never()) + ->method('invalidateTags'); + $entity = $this->getMockEntity(); $this->entityStorage->save($entity); } @@ -383,6 +431,9 @@ public function testSaveDuplicate() { $config_object->expects($this->never()) ->method('save'); + $this->cacheBackend->expects($this->never()) + ->method('invalidateTags'); + $this->configFactory->expects($this->once()) ->method('get') ->with('the_config_prefix.foo') @@ -410,6 +461,9 @@ public function testSaveMismatch() { $config_object->expects($this->never()) ->method('save'); + $this->cacheBackend->expects($this->never()) + ->method('invalidateTags'); + $this->configFactory->expects($this->once()) ->method('get') ->with('the_config_prefix.foo') @@ -439,6 +493,12 @@ public function testSaveNoMismatch() { $config_object->expects($this->once()) ->method('save'); + $this->cacheBackend->expects($this->once()) + ->method('invalidateTags') + ->with(array( + $this->entityTypeId . 's' => TRUE, // List cache tag. + )); + $this->configFactory->expects($this->once()) ->method('get') ->with('the_config_prefix.baz') @@ -482,6 +542,9 @@ public function testSaveChangedUuid() { array('id', 'foo'), ))); + $this->cacheBackend->expects($this->never()) + ->method('invalidateTags'); + $this->configFactory->expects($this->at(1)) ->method('loadMultiple') ->with(array('the_config_prefix.foo')) @@ -643,6 +706,9 @@ public function testLoadRevision() { * @covers ::deleteRevision() */ public function testDeleteRevision() { + $this->cacheBackend->expects($this->never()) + ->method('invalidateTags'); + $this->assertSame(NULL, $this->entityStorage->deleteRevision(1)); } @@ -669,6 +735,13 @@ public function testDelete() { $config_map[] = array("the_config_prefix.$id", $config_object); } + $this->cacheBackend->expects($this->once()) + ->method('invalidateTags') + ->with(array( + $this->entityTypeId . 's' => TRUE, // List cache tag. + $this->entityTypeId => array('foo', 'bar'), // Own cache tag. + )); + $this->configFactory->expects($this->exactly(2)) ->method('get') ->will($this->returnValueMap($config_map)); @@ -710,6 +783,9 @@ public function testDeleteNothing() { $this->configFactory->expects($this->never()) ->method('get'); + $this->cacheBackend->expects($this->never()) + ->method('invalidateTags'); + $this->entityStorage->delete(array()); } diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php index 47c9ad4..6ccad14 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php @@ -68,6 +68,13 @@ class EntityUnitTest extends UnitTestCase { protected $languageManager; /** + * The mocked cache backend. + * + * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $cacheBackend; + + /** * The entity values. * * @var array @@ -112,11 +119,14 @@ public function setUp() { ->with('en') ->will($this->returnValue(new Language(array('id' => 'en')))); + $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); + $container = new ContainerBuilder(); $container->set('entity.manager', $this->entityManager); $container->set('uuid', $this->uuid); $container->set('language_manager', $this->languageManager); - + $container->set('cache.test', $this->cacheBackend); + $container->setParameter('cache_bins', array('cache.test' => 'test')); \Drupal::setContainer($container); $this->entity = $this->getMockForAbstractClass('\Drupal\Core\Entity\Entity', array($this->values, $this->entityTypeId)); @@ -280,9 +290,26 @@ public function testPreSave() { * @covers ::postSave */ public function testPostSave() { + $this->cacheBackend->expects($this->at(0)) + ->method('invalidateTags') + ->with(array( + $this->entityTypeId . 's' => TRUE, // List cache tag. + )); + $this->cacheBackend->expects($this->at(1)) + ->method('invalidateTags') + ->with(array( + $this->entityTypeId . 's' => TRUE, // List cache tag. + $this->entityTypeId => array($this->values['id']), // Own cache tag. + )); + // This method is internal, so check for errors on calling it only. $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface'); - $this->entity->postSave($storage); + + // A creation should trigger the invalidation of the "list" cache tag. + $this->entity->postSave($storage, FALSE); + // An update should trigger the invalidation of both the "list" and the + // "own" cache tags. + $this->entity->postSave($storage, TRUE); } /** @@ -317,16 +344,23 @@ public function testPreDelete() { * @covers ::postDelete */ public function testPostDelete() { + $this->cacheBackend->expects($this->once()) + ->method('invalidateTags') + ->with(array( + $this->entityTypeId => array($this->values['id']), + $this->entityTypeId . 's' => TRUE, + )); $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface'); $entity = $this->getMockBuilder('\Drupal\Core\Entity\Entity') + ->setConstructorArgs(array($this->values, $this->entityTypeId)) ->setMethods(array('onSaveOrDelete')) - ->disableOriginalConstructor() ->getMock(); $entity->expects($this->once()) ->method('onSaveOrDelete'); - $this->entity->postDelete($storage, array($entity)); + $entities = array($this->values['id'] => $entity); + $this->entity->postDelete($storage, $entities); } /**