.../lib/Drupal/Core/Datetime/Entity/DateFormat.php | 2 +- core/lib/Drupal/Core/Entity/Entity.php | 31 +---- core/lib/Drupal/Core/Entity/EntityInterface.php | 2 +- .../Drupal/Core/Entity/EntityStorageInterface.php | 2 +- core/lib/Drupal/Core/Entity/EntityViewBuilder.php | 15 ++- core/modules/aggregator/src/Entity/Item.php | 4 +- core/modules/block/src/BlockViewBuilder.php | 1 - core/modules/block/src/Entity/Block.php | 27 +++- core/modules/book/book.module | 6 + core/modules/book/src/BookExport.php | 4 + .../modules/book/src/Controller/BookController.php | 4 + .../FieldFormatter/CommentDefaultFormatter.php | 30 +++-- .../Tests/CommentDefaultFormatterCacheTagsTest.php | 4 +- .../tests/src/Unit/Entity/CommentLockTest.php | 8 +- .../tests/config_test/src/Entity/ConfigTest.php | 7 ++ core/modules/filter/filter.module | 3 +- core/modules/filter/src/FilterPluginManager.php | 3 +- core/modules/hal/src/Tests/EntityTest.php | 26 +++- core/modules/shortcut/src/Entity/Shortcut.php | 4 +- .../src/Tests/Cache/PageCacheTagsTestBase.php | 8 ++ .../src/Tests/Entity/EntityCacheTagsTestBase.php | 136 ++++++++++++++++++--- .../src/Tests/Entity/EntityViewBuilderTest.php | 4 +- .../modules/entity_test/entity_test.routing.yml | 17 +++ .../src/Controller/EntityTestController.php | 71 +++++++++++ core/modules/toolbar/toolbar.module | 5 +- core/modules/user/src/Tests/UserCacheTagsTest.php | 7 ++ core/modules/user/src/Tests/UserPictureTest.php | 8 +- .../src/Plugin/views/cache/CachePluginBase.php | 5 +- core/modules/views_ui/src/ViewUI.php | 4 +- .../views_ui/tests/src/Unit/ViewUIObjectTest.php | 2 +- .../Core/Config/Entity/ConfigEntityStorageTest.php | 17 ++- .../Drupal/Tests/Core/Entity/EntityUnitTest.php | 30 +++-- .../KeyValueStore/KeyValueEntityStorageTest.php | 1 - 33 files changed, 384 insertions(+), 114 deletions(-) diff --git a/core/lib/Drupal/Core/Datetime/Entity/DateFormat.php b/core/lib/Drupal/Core/Datetime/Entity/DateFormat.php index bb4efe0..63ec0d1 100644 --- a/core/lib/Drupal/Core/Datetime/Entity/DateFormat.php +++ b/core/lib/Drupal/Core/Datetime/Entity/DateFormat.php @@ -101,7 +101,7 @@ public function getCacheTag() { /** * {@inheritdoc} */ - public function getListCacheTags() { + public static function getListCacheTags() { return ['rendered']; } diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php index fc289d2..533c71a 100644 --- a/core/lib/Drupal/Core/Entity/Entity.php +++ b/core/lib/Drupal/Core/Entity/Entity.php @@ -357,7 +357,6 @@ public function preSave(EntityStorageInterface $storage) { * {@inheritdoc} */ public function postSave(EntityStorageInterface $storage, $update = TRUE) { - $this->onSaveOrDelete(); $this->invalidateTagsOnSave($update); } @@ -409,9 +408,10 @@ public function getCacheTag() { /** * {@inheritdoc} */ - public function getListCacheTags() { + public static function getListCacheTags() { // @todo Add bundle-specific listing cache tag? https://drupal.org/node/2145751 - return [$this->entityTypeId . 's']; + $entity_type_id = \Drupal::entityManager()->getEntityTypeFromClass(get_called_class()); + return [$entity_type_id . '_list']; } /** @@ -438,26 +438,6 @@ public static function create(array $values = array()) { return $entity_manager->getStorage($entity_manager->getEntityTypeFromClass(get_called_class()))->create($values); } - - /** - * Acts on an entity after it was saved or deleted. - */ - protected function onSaveOrDelete() { - $referenced_entities = array( - $this->getEntityTypeId() => array($this->id() => $this), - ); - - foreach ($this->referencedEntities() as $referenced_entity) { - $referenced_entities[$referenced_entity->getEntityTypeId()][$referenced_entity->id()] = $referenced_entity; - } - - foreach ($referenced_entities as $entity_type => $entities) { - if ($this->entityManager()->hasHandler($entity_type, 'view_builder')) { - $this->entityManager()->getViewBuilder($entity_type)->resetCache($entities); - } - } - } - /** * Invalidates an entity's cache tags upon save. * @@ -485,15 +465,14 @@ protected function invalidateTagsOnSave($update) { * An array of entities. */ protected static function invalidateTagsOnDelete(array $entities) { - $tags = array(); + $tags = static::getListCacheTags(); 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 = Cache::mergeTags($tags, $entity->getCacheTag(), $entity->getListCacheTags()); - $entity->onSaveOrDelete(); + $tags = Cache::mergeTags($tags, $entity->getCacheTag()); } Cache::invalidateTags($tags); } diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php index 4399d01..c730e54 100644 --- a/core/lib/Drupal/Core/Entity/EntityInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityInterface.php @@ -375,6 +375,6 @@ public function getCacheTag(); * @return array * An array of cache tags. */ - public function getListCacheTags(); + public static function getListCacheTags(); } diff --git a/core/lib/Drupal/Core/Entity/EntityStorageInterface.php b/core/lib/Drupal/Core/Entity/EntityStorageInterface.php index 7a329cc..082e989 100644 --- a/core/lib/Drupal/Core/Entity/EntityStorageInterface.php +++ b/core/lib/Drupal/Core/Entity/EntityStorageInterface.php @@ -45,7 +45,7 @@ public function resetCache(array $ids = NULL); * @param $ids * An array of entity IDs, or NULL to load all entities. * - * @return array + * @return \Drupal\Core\Entity\EntityInterface[] * An array of entity objects indexed by their IDs. Returns an empty array * if no matching entities found. */ diff --git a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php index e24735a..c1a306a 100644 --- a/core/lib/Drupal/Core/Entity/EntityViewBuilder.php +++ b/core/lib/Drupal/Core/Entity/EntityViewBuilder.php @@ -349,11 +349,20 @@ public function getCacheTag() { * {@inheritdoc} */ public function resetCache(array $entities = NULL) { + // If no set of specific entities is provided, invalidate the entity view + // builder's cache tag. This will invalidate all entities rendered by this + // view builder. + // Otherwise, if a set of specific entities is provided, invalidate those + // specific entities only, plus their list cache tags, because any lists in + // which these entities are rendered, must be invalidated as well. However, + // even in this case, we might invalidate more cache items than necessary. + // When we have a way to invalidate only those cache items that have both + // the individual entity's cache tag and the view builder's cache tag, we'll + // be able to optimize this further. if (isset($entities)) { - // Always invalidate the ENTITY_TYPE_list tag. - $tags = array($this->entityTypeId . '_list'); + $tags = []; foreach ($entities as $entity) { - $tags = Cache::mergeTags($tags, $entity->getCacheTag()); + $tags = Cache::mergeTags($tags, $entity->getCacheTag(), $entity->getListCacheTags()); } Cache::invalidateTags($tags); } diff --git a/core/modules/aggregator/src/Entity/Item.php b/core/modules/aggregator/src/Entity/Item.php index 5df7013..341732a 100644 --- a/core/modules/aggregator/src/Entity/Item.php +++ b/core/modules/aggregator/src/Entity/Item.php @@ -236,8 +236,8 @@ public function getCacheTag() { /** * {@inheritdoc} */ - public function getListCacheTags() { - return Feed::load($this->getFeedId())->getListCacheTags(); + public static function getListCacheTags() { + return Feed::getListCacheTags(); } diff --git a/core/modules/block/src/BlockViewBuilder.php b/core/modules/block/src/BlockViewBuilder.php index d29b63c..32d75cf 100644 --- a/core/modules/block/src/BlockViewBuilder.php +++ b/core/modules/block/src/BlockViewBuilder.php @@ -73,7 +73,6 @@ public function viewMultiple(array $entities = array(), $view_mode = 'full', $la $build[$entity_id]['#cache']['tags'] = Cache::mergeTags( $this->getCacheTag(), // Block view builder cache tag. $entity->getCacheTag(), // Block entity cache tag. - $entity->getListCacheTags(), // Block entity list cache tags. $plugin->getCacheTags() // Block plugin cache tags. ); diff --git a/core/modules/block/src/Entity/Block.php b/core/modules/block/src/Entity/Block.php index 0c71ed1..c39f3cf 100644 --- a/core/modules/block/src/Entity/Block.php +++ b/core/modules/block/src/Entity/Block.php @@ -157,14 +157,31 @@ public function calculateDependencies() { /** * {@inheritdoc} + */ + public function postSave(EntityStorageInterface $storage, $update = TRUE) { + parent::postSave($storage, $update); + + // Entity::postSave() calls Entity::invalidateTagsOnSave(), which only + // handles the regular cases. The Block entity has one special case: a + // newly created block may *also* appear on any page in the current theme, + // so we must invalidate the associated block's cache tag (which includes + // the theme cache tag). + if (!$update) { + Cache::invalidateTags($this->getCacheTag()); + } + } + + /** + * {@inheritdoc} * * Block configuration entities are a special case: one block entity stores - * the placement of one block in one theme. Instead of using an entity type- - * specific list cache tag like most entities, use the cache tag of the theme - * this block is placed in instead. + * the placement of one block in one theme. Changing these entities may affect + * any page that is rendered in a certain theme, even if the block doesn't + * appear there currently. Hence a block configuration entity must also return + * the associated theme's cache tag. */ - public function getListCacheTags() { - return array('theme:' . $this->theme); + public function getCacheTag() { + return Cache::mergeTags(parent::getCacheTag(), ['theme:' . $this->theme]); } /** diff --git a/core/modules/book/book.module b/core/modules/book/book.module index 861ed73..ac9272d 100644 --- a/core/modules/book/book.module +++ b/core/modules/book/book.module @@ -14,6 +14,7 @@ use Drupal\Core\Routing\RouteMatchInterface; use Drupal\node\NodeInterface; use Drupal\node\NodeTypeInterface; +use Drupal\node\Entity\Node; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Entity\Display\EntityViewDisplayInterface; use Drupal\Core\Template\Attribute; @@ -243,6 +244,11 @@ function book_node_view(array &$build, EntityInterface $node, EntityViewDisplayI drupal_get_path('module', 'book') . '/css/book.theme.css', ), ), + // The book navigation is a listing of Node entities, so associate its + // list cache tag for correct invalidation. + '#cache' => [ + 'tags' => $node->getListCacheTags(), + ], ); } } diff --git a/core/modules/book/src/BookExport.php b/core/modules/book/src/BookExport.php index 4b45078..543b3b3 100644 --- a/core/modules/book/src/BookExport.php +++ b/core/modules/book/src/BookExport.php @@ -8,6 +8,7 @@ namespace Drupal\book; use Drupal\Core\Entity\EntityManagerInterface; +use Drupal\node\Entity\Node; use Drupal\node\NodeInterface; /** @@ -84,6 +85,9 @@ public function bookExportHtml(NodeInterface $node) { '#title' => $node->label(), '#contents' => $contents, '#depth' => $node->book['depth'], + '#cache' => [ + 'tags' => $node->getListCacheTags(), + ], ); } diff --git a/core/modules/book/src/Controller/BookController.php b/core/modules/book/src/Controller/BookController.php index 305a0b9..c1104f1 100644 --- a/core/modules/book/src/Controller/BookController.php +++ b/core/modules/book/src/Controller/BookController.php @@ -10,6 +10,7 @@ use Drupal\book\BookExport; use Drupal\book\BookManagerInterface; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; +use Drupal\node\Entity\Node; use Drupal\node\NodeInterface; use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -110,6 +111,9 @@ public function bookRender() { return array( '#theme' => 'item_list', '#items' => $book_list, + '#cache' => [ + 'tags' => Node::getListCacheTags(), + ], ); } diff --git a/core/modules/comment/src/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php b/core/modules/comment/src/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php index aa41d12..16acc2c 100644 --- a/core/modules/comment/src/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php +++ b/core/modules/comment/src/Plugin/Field/FieldFormatter/CommentDefaultFormatter.php @@ -9,6 +9,7 @@ use Drupal\comment\CommentManagerInterface; use Drupal\comment\CommentStorageInterface; +use Drupal\comment\Entity\Comment; use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface; use Drupal\Core\Entity\EntityViewBuilderInterface; use Drupal\Core\Entity\EntityFormBuilderInterface; @@ -150,19 +151,24 @@ public function viewElements(FieldItemListInterface $items) { // Unpublished comments are not included in // $entity->get($field_name)->comment_count, but unpublished comments // should display if the user is an administrator. - if ((($entity->get($field_name)->comment_count && $this->currentUser->hasPermission('access comments')) || - $this->currentUser->hasPermission('administer comments'))) { - $mode = $comment_settings['default_mode']; - $comments_per_page = $comment_settings['per_page']; - $comments = $this->storage->loadThread($entity, $field_name, $mode, $comments_per_page, $this->getSetting('pager_id')); - if ($comments) { - comment_prepare_thread($comments); - $build = $this->viewBuilder->viewMultiple($comments); - $build['pager']['#theme'] = 'pager'; - if ($this->getSetting('pager_id')) { - $build['pager']['#element'] = $this->getSetting('pager_id'); + if ($this->currentUser->hasPermission('access comments') || $this->currentUser->hasPermission('administer comments')) { + // This is a listing of Comment entities, so associate its list cache + // tag for correct invalidation. + $output['comments']['#cache']['tags'] = Comment::getListCacheTags(); + + if ($entity->get($field_name)->comment_count || $this->currentUser->hasPermission('administer comments')) { + $mode = $comment_settings['default_mode']; + $comments_per_page = $comment_settings['per_page']; + $comments = $this->storage->loadThread($entity, $field_name, $mode, $comments_per_page, $this->getSetting('pager_id')); + if ($comments) { + comment_prepare_thread($comments); + $build = $this->viewBuilder->viewMultiple($comments); + $build['pager']['#theme'] = 'pager'; + if ($this->getSetting('pager_id')) { + $build['pager']['#element'] = $this->getSetting('pager_id'); + } + $output['comments'] += $build; } - $output['comments'] = $build; } } diff --git a/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php b/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php index 9c14685..8747737f2d 100644 --- a/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php +++ b/core/modules/comment/src/Tests/CommentDefaultFormatterCacheTagsTest.php @@ -68,6 +68,7 @@ public function testCacheTags() { $expected_cache_tags = array( 'entity_test_view', 'entity_test:' . $commented_entity->id(), + 'comment_list', ); sort($expected_cache_tags); $this->assertEqual($build['#cache']['tags'], $expected_cache_tags, 'The test entity has the expected cache tags before it has comments.'); @@ -96,7 +97,7 @@ public function testCacheTags() { // https://drupal.org/node/597236 lands, it's a temporary work-around. $commented_entity = entity_load('entity_test', $commented_entity->id(), TRUE); - // Verify cache tags on the rendered entity before it has comments. + // Verify cache tags on the rendered entity when it has comments. $build = \Drupal::entityManager() ->getViewBuilder('entity_test') ->view($commented_entity); @@ -104,6 +105,7 @@ public function testCacheTags() { $expected_cache_tags = array( 'entity_test_view', 'entity_test:' . $commented_entity->id(), + 'comment_list', 'comment_view', 'comment:' . $comment->id(), 'filter_format:plain_text', diff --git a/core/modules/comment/tests/src/Unit/Entity/CommentLockTest.php b/core/modules/comment/tests/src/Unit/Entity/CommentLockTest.php index aef189e..c6f11bc 100644 --- a/core/modules/comment/tests/src/Unit/Entity/CommentLockTest.php +++ b/core/modules/comment/tests/src/Unit/Entity/CommentLockTest.php @@ -49,8 +49,8 @@ public function testLocks() { $methods = get_class_methods('Drupal\comment\Entity\Comment'); unset($methods[array_search('preSave', $methods)]); unset($methods[array_search('postSave', $methods)]); - $methods[] = 'onSaveOrDelete'; $methods[] = 'onUpdateBundleEntity'; + $methods[] = 'invalidateTagsOnSave'; $comment = $this->getMockBuilder('Drupal\comment\Entity\Comment') ->disableOriginalConstructor() ->setMethods($methods) @@ -79,12 +79,6 @@ 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:' . $cid))); - $comment->expects($this->once()) - ->method('getListCacheTags') - ->will($this->returnValue(array('comments'))); $storage = $this->getMock('Drupal\comment\CommentStorageInterface'); // preSave() should acquire the lock. (This is what's really being tested.) diff --git a/core/modules/config/tests/config_test/src/Entity/ConfigTest.php b/core/modules/config/tests/config_test/src/Entity/ConfigTest.php index b022cc6..c30f912 100644 --- a/core/modules/config/tests/config_test/src/Entity/ConfigTest.php +++ b/core/modules/config/tests/config_test/src/Entity/ConfigTest.php @@ -156,4 +156,11 @@ public function onDependencyRemoval(array $dependencies) { } } + /** + * {@inheritdoc} + */ + public static function getListCacheTags() { + return ['config_test_list']; + } + } diff --git a/core/modules/filter/filter.module b/core/modules/filter/filter.module index 99d6194..0781f87 100644 --- a/core/modules/filter/filter.module +++ b/core/modules/filter/filter.module @@ -14,6 +14,7 @@ use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Template\Attribute; +use Drupal\filter\Entity\FilterFormat; use Drupal\filter\FilterFormatInterface; /** @@ -139,7 +140,7 @@ function filter_formats(AccountInterface $account = NULL) { else { $formats['all'] = \Drupal::entityManager()->getStorage('filter_format')->loadByProperties(array('status' => TRUE)); uasort($formats['all'], 'Drupal\Core\Config\Entity\ConfigEntityBase::sort'); - \Drupal::cache()->set("filter_formats:{$language_interface->id}", $formats['all'], Cache::PERMANENT, array('filter_formats')); + \Drupal::cache()->set("filter_formats:{$language_interface->id}", $formats['all'], Cache::PERMANENT, FilterFormat::getListCacheTags()); } } diff --git a/core/modules/filter/src/FilterPluginManager.php b/core/modules/filter/src/FilterPluginManager.php index 43b6568..fbb6cc8 100644 --- a/core/modules/filter/src/FilterPluginManager.php +++ b/core/modules/filter/src/FilterPluginManager.php @@ -11,6 +11,7 @@ use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Plugin\DefaultPluginManager; +use Drupal\filter\Entity\FilterFormat; /** * Manages text processing filters. @@ -37,7 +38,7 @@ class FilterPluginManager extends DefaultPluginManager implements FallbackPlugin public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) { parent::__construct('Plugin/Filter', $namespaces, $module_handler, 'Drupal\filter\Plugin\FilterInterface', 'Drupal\filter\Annotation\Filter'); $this->alterInfo('filter_info'); - $this->setCacheBackend($cache_backend, 'filter_plugins', array('filter_formats')); + $this->setCacheBackend($cache_backend, 'filter_plugins', FilterFormat::getListCacheTags()); } /** diff --git a/core/modules/hal/src/Tests/EntityTest.php b/core/modules/hal/src/Tests/EntityTest.php index 591f7ff..47aa4d3 100644 --- a/core/modules/hal/src/Tests/EntityTest.php +++ b/core/modules/hal/src/Tests/EntityTest.php @@ -64,7 +64,8 @@ public function testNode() { 'body' => array( 'value' => $this->randomMachineName(), 'format' => $this->randomMachineName(), - ) + ), + 'revision_log' => $this->randomString(), )); $node->save(); @@ -160,13 +161,32 @@ public function testComment() { )); $node->save(); + $parent_comment = entity_create('comment', array( + 'uid' => $user->id(), + 'subject' => $this->randomMachineName(), + 'comment_body' => [ + 'value' => $this->randomMachineName(), + 'format' => NULL, + ], + 'entity_id' => $node->id(), + 'entity_type' => 'node', + 'field_name' => 'comment', + )); + $parent_comment->save(); + $comment = entity_create('comment', array( 'uid' => $user->id(), 'subject' => $this->randomMachineName(), - 'comment_body' => $this->randomMachineName(), + 'comment_body' => [ + 'value' => $this->randomMachineName(), + 'format' => NULL, + ], 'entity_id' => $node->id(), 'entity_type' => 'node', - 'field_name' => 'comment' + 'field_name' => 'comment', + 'pid' => $parent_comment->id(), + 'mail' => 'dries@drupal.org', + 'homepage' => 'http://buytaert.net', )); $comment->save(); diff --git a/core/modules/shortcut/src/Entity/Shortcut.php b/core/modules/shortcut/src/Entity/Shortcut.php index a3e15e7..5f49e4e 100644 --- a/core/modules/shortcut/src/Entity/Shortcut.php +++ b/core/modules/shortcut/src/Entity/Shortcut.php @@ -230,8 +230,8 @@ public function getCacheTag() { /** * {@inheritdoc} */ - public function getListCacheTags() { - return $this->shortcut_set->entity->getListCacheTags(); + public static function getListCacheTags() { + return ShortcutSet::getListCacheTags(); } } diff --git a/core/modules/system/src/Tests/Cache/PageCacheTagsTestBase.php b/core/modules/system/src/Tests/Cache/PageCacheTagsTestBase.php index f3a0d76..7e7ef32 100644 --- a/core/modules/system/src/Tests/Cache/PageCacheTagsTestBase.php +++ b/core/modules/system/src/Tests/Cache/PageCacheTagsTestBase.php @@ -17,6 +17,13 @@ /** * {@inheritdoc} + * + * Always enable header dumping in page cache tags tests, this aids debugging. + */ + protected $dumpHeaders = TRUE; + + /** + * {@inheritdoc} */ protected function setUp() { parent::setUp(); @@ -50,6 +57,7 @@ protected function verifyPageCache($path, $hit_or_miss, $tags = FALSE) { $cid = sha1(implode(':', $cid_parts)); $cache_entry = \Drupal::cache('render')->get($cid); sort($cache_entry->tags); + $tags = array_unique($tags); sort($tags); $this->assertIdentical($cache_entry->tags, $tags); } diff --git a/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php b/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php index aeaf266..b4188c8 100644 --- a/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php +++ b/core/modules/system/src/Tests/Entity/EntityCacheTagsTestBase.php @@ -138,6 +138,19 @@ protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) { } /** + * Returns the additional cache tags for the tested entity's listing by type. + * + * Necessary when there are unavoidable default entities of this type, e.g. + * the anonymous and administrator User entities always exist. + * + * @return array + * An array of the additional cache tags. + */ + protected function getAdditionalCacheTagsForEntityListing() { + return []; + } + + /** * Selects the preferred view mode for the given entity type. * * Prefers 'full', picks the first one otherwise, and if none are available, @@ -253,16 +266,19 @@ protected function createReferenceTestEntities($referenced_entity) { * Tests cache tags presence and invalidation of the entity when referenced. * * Tests the following cache tags: - * - "_view" - * - ":" - * - "_view" - * * - ":" + * - entity type view cache tag: "_view" + * - entity cache tag: ":" + * - entity type list cache tag: "_list" + * - referencing entity type view cache tag: "_view" + * - referencing entity type cache tag: ":" */ 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(); + $empty_entity_listing_path = 'entity_test/list_empty/' . $entity_type; + $nonempty_entity_listing_path = 'entity_test/list_labels_alphabetically/' . $entity_type; $render_cache_tags = array('rendered'); $theme_cache_tags = array('theme:stark', 'theme_global_settings'); @@ -287,6 +303,21 @@ public function testReferencedEntity() { \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTag() ); + // Generate the cache tags for all two possible entity listing paths. + // 1. list cache tag only (listing query has no match) + // 2. list cache tag plus entity cache tag (listing query has a match) + $empty_entity_listing_cache_tags = Cache::mergeTags( + $this->entity->getListCacheTags(), + $theme_cache_tags, + $render_cache_tags + ); + $nonempty_entity_listing_cache_tags = Cache::mergeTags( + $this->entity->getListCacheTags(), + $this->entity->getCacheTag(), + $this->getAdditionalCacheTagsForEntityListing($this->entity), + $theme_cache_tags, + $render_cache_tags + ); $this->pass("Test referencing entity.", 'Debug'); $this->verifyPageCache($referencing_entity_path, 'MISS'); @@ -317,41 +348,62 @@ public function testReferencedEntity() { $this->verifyPageCache($listing_path, 'HIT', $tags); + $this->pass("Test empty listing.", 'Debug'); + // Prime the page cache for the empty listing. + $this->verifyPageCache($empty_entity_listing_path, 'MISS'); + // Verify a cache hit, but also the presence of the correct cache tags. + $this->verifyPageCache($empty_entity_listing_path, 'HIT', $empty_entity_listing_cache_tags); + + + $this->pass("Test listing containing referenced entity.", 'Debug'); + // Prime the page cache for the listing containing the referenced entity. + $this->verifyPageCache($nonempty_entity_listing_path, 'MISS'); + // Verify a cache hit, but also the presence of the correct cache tags. + $this->verifyPageCache($nonempty_entity_listing_path, 'HIT', $nonempty_entity_listing_cache_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. + // for every route except the one 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($empty_entity_listing_path, 'MISS'); + $this->verifyPageCache($nonempty_entity_listing_path, 'MISS'); $this->verifyPageCache($non_referencing_entity_path, 'HIT'); // Verify cache hits. $this->verifyPageCache($referencing_entity_path, 'HIT'); $this->verifyPageCache($listing_path, 'HIT'); + $this->verifyPageCache($empty_entity_listing_path, 'HIT'); + $this->verifyPageCache($nonempty_entity_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. + // for every route except the ones for the non-referencing entity and the + // empty entity listing. $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($nonempty_entity_listing_path, 'HIT'); $this->verifyPageCache($non_referencing_entity_path, 'HIT'); + $this->verifyPageCache($empty_entity_listing_path, 'HIT'); // Verify cache hits. $this->verifyPageCache($referencing_entity_path, 'HIT'); $this->verifyPageCache($listing_path, 'HIT'); + $this->verifyPageCache($nonempty_entity_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. + // miss only for the non-referencing entity route. $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($empty_entity_listing_path, 'HIT'); + $this->verifyPageCache($nonempty_entity_listing_path, 'HIT'); $this->verifyPageCache($non_referencing_entity_path, 'MISS'); // Verify cache hits. @@ -361,7 +413,7 @@ public function testReferencedEntity() { if ($this->entity->getEntityType()->hasHandlerClass('view_builder')) { // Verify that after modifying the entity's display, there is a cache miss // for both the referencing entity, and the listing of referencing - // entities, but not for the non-referencing entity. + // entities, but not for any other routes. $referenced_entity_view_mode = $this->selectViewMode($this->entity->getEntityTypeId()); $this->pass("Test modification of referenced entity's '$referenced_entity_view_mode' display.", 'Debug'); $entity_display = entity_get_display($entity_type, $this->entity->bundle(), $referenced_entity_view_mode); @@ -369,6 +421,8 @@ public function testReferencedEntity() { $this->verifyPageCache($referencing_entity_path, 'MISS'); $this->verifyPageCache($listing_path, 'MISS'); $this->verifyPageCache($non_referencing_entity_path, 'HIT'); + $this->verifyPageCache($empty_entity_listing_path, 'HIT'); + $this->verifyPageCache($nonempty_entity_listing_path, 'HIT'); // Verify cache hits. $this->verifyPageCache($referencing_entity_path, 'HIT'); @@ -380,17 +434,32 @@ public function testReferencedEntity() { 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. + // referencing entities, but not for any other routes. $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'); + // Special case: entity types may choose to use their bundle entity type + // cache tags, to avoid having excessively granular invalidation. + $is_special_case = $bundle_entity->getCacheTag() == $this->entity->getCacheTag() && $bundle_entity->getListCacheTags() == $this->entity->getListCacheTags(); + if ($is_special_case) { + $this->verifyPageCache($empty_entity_listing_path, 'MISS'); + $this->verifyPageCache($nonempty_entity_listing_path, 'MISS'); + } + else { + $this->verifyPageCache($empty_entity_listing_path, 'HIT'); + $this->verifyPageCache($nonempty_entity_listing_path, 'HIT'); + } // Verify cache hits. $this->verifyPageCache($referencing_entity_path, 'HIT'); $this->verifyPageCache($listing_path, 'HIT'); + if ($is_special_case) { + $this->verifyPageCache($empty_entity_listing_path, 'HIT'); + $this->verifyPageCache($nonempty_entity_listing_path, 'HIT'); + } } @@ -403,6 +472,8 @@ public function testReferencedEntity() { $field_storage->save(); $this->verifyPageCache($referencing_entity_path, 'MISS'); $this->verifyPageCache($listing_path, 'MISS'); + $this->verifyPageCache($empty_entity_listing_path, 'HIT'); + $this->verifyPageCache($nonempty_entity_listing_path, 'HIT'); $this->verifyPageCache($non_referencing_entity_path, 'HIT'); // Verify cache hits. @@ -418,6 +489,8 @@ public function testReferencedEntity() { $field->save(); $this->verifyPageCache($referencing_entity_path, 'MISS'); $this->verifyPageCache($listing_path, 'MISS'); + $this->verifyPageCache($empty_entity_listing_path, 'HIT'); + $this->verifyPageCache($nonempty_entity_listing_path, 'HIT'); $this->verifyPageCache($non_referencing_entity_path, 'HIT'); // Verify cache hits. @@ -426,42 +499,63 @@ public function testReferencedEntity() { } - // 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. + // Verify that after invalidating the entity's cache tag directly, there is + // a cache miss for every route except the ones for the non-referencing + // entity and the empty entity listing. $this->pass("Test invalidation of referenced entity's cache tag.", 'Debug'); Cache::invalidateTags($this->entity->getCacheTag()); $this->verifyPageCache($referencing_entity_path, 'MISS'); $this->verifyPageCache($listing_path, 'MISS'); + $this->verifyPageCache($nonempty_entity_listing_path, 'MISS'); $this->verifyPageCache($non_referencing_entity_path, 'HIT'); + $this->verifyPageCache($empty_entity_listing_path, 'HIT'); // Verify cache hits. $this->verifyPageCache($referencing_entity_path, 'HIT'); $this->verifyPageCache($listing_path, 'HIT'); + $this->verifyPageCache($nonempty_entity_listing_path, 'HIT'); + + // Verify that after invalidating the entity's list cache tag directly, + // there is a cache miss for both the empty entity listing and the non-empty + // entity listing routes, but not for other routes. + $this->pass("Test invalidation of referenced entity's list cache tag.", 'Debug'); + Cache::invalidateTags($this->entity->getListCacheTags()); + $this->verifyPageCache($empty_entity_listing_path, 'MISS'); + $this->verifyPageCache($nonempty_entity_listing_path, 'MISS'); + $this->verifyPageCache($referencing_entity_path, 'HIT'); + $this->verifyPageCache($non_referencing_entity_path, 'HIT'); + $this->verifyPageCache($listing_path, 'HIT'); + + // Verify cache hits. + $this->verifyPageCache($empty_entity_listing_path, 'HIT'); + $this->verifyPageCache($nonempty_entity_listing_path, 'HIT'); if (!empty($view_cache_tag)) { // 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. + // listing of referencing entities, but not for other routes. $this->pass("Test invalidation of referenced entity's 'view' cache tag.", 'Debug'); Cache::invalidateTags($view_cache_tag); $this->verifyPageCache($referencing_entity_path, 'MISS'); $this->verifyPageCache($listing_path, 'MISS'); $this->verifyPageCache($non_referencing_entity_path, 'HIT'); + $this->verifyPageCache($empty_entity_listing_path, 'HIT'); + $this->verifyPageCache($nonempty_entity_listing_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. + // Verify that after deleting the entity, there is a cache miss for every + // route except for the the non-referencing entity one. $this->pass('Test deletion of referenced entity.', 'Debug'); $this->entity->delete(); $this->verifyPageCache($referencing_entity_path, 'MISS'); $this->verifyPageCache($listing_path, 'MISS'); + $this->verifyPageCache($empty_entity_listing_path, 'MISS'); + $this->verifyPageCache($nonempty_entity_listing_path, 'MISS'); $this->verifyPageCache($non_referencing_entity_path, 'HIT'); // Verify cache hits. @@ -473,6 +567,10 @@ public function testReferencedEntity() { $this->verifyPageCache($referencing_entity_path, 'HIT', $tags); $tags = Cache::mergeTags($render_cache_tags, $theme_cache_tags); $this->verifyPageCache($listing_path, 'HIT', $tags); + $tags = Cache::mergeTags($render_cache_tags, $theme_cache_tags, $this->entity->getListCacheTags()); + $this->verifyPageCache($empty_entity_listing_path, 'HIT', $tags); + $tags = Cache::mergeTags($tags, $this->getAdditionalCacheTagsForEntityListing()); + $this->verifyPageCache($nonempty_entity_listing_path, 'HIT', $tags); } /** diff --git a/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php b/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php index 547663c..d57b79c 100644 --- a/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php +++ b/core/modules/system/src/Tests/Entity/EntityViewBuilderTest.php @@ -87,6 +87,7 @@ public function testEntityViewBuilderCacheWithReferences() { // Create an entity reference field and an entity that will be referenced. entity_reference_create_field('entity_test', 'entity_test', 'reference_field', 'Reference', 'entity_test'); entity_get_display('entity_test', 'entity_test', 'full')->setComponent('reference_field', [ + 'type' => 'entity_reference_entity_view', 'settings' => ['link' => FALSE], ])->save(); $entity_test_reference = $this->createTestEntity('entity_test'); @@ -108,6 +109,7 @@ public function testEntityViewBuilderCacheWithReferences() { // Create another entity that references the first one. $entity_test = $this->createTestEntity('entity_test'); $entity_test->reference_field->entity = $entity_test_reference; + $entity_test->reference_field->access = TRUE; $entity_test->save(); // Get a fully built entity view render array. @@ -124,7 +126,7 @@ public function testEntityViewBuilderCacheWithReferences() { $this->assertTrue($this->container->get('cache.' . $bin)->get($cid), 'The entity render element has been cached.'); // Save the entity and verify that both cache entries have been deleted. - $entity_test->save(); + $entity_test_reference->save(); $this->assertFalse($this->container->get('cache.' . $bin)->get($cid), 'The entity render cache has been cleared when the entity was deleted.'); $this->assertFalse($this->container->get('cache.' . $bin_reference)->get($cid_reference), 'The entity render cache for the referenced entity has been cleared when the entity was deleted.'); diff --git a/core/modules/system/tests/modules/entity_test/entity_test.routing.yml b/core/modules/system/tests/modules/entity_test/entity_test.routing.yml index 25c159d..e3d0adc 100644 --- a/core/modules/system/tests/modules/entity_test/entity_test.routing.yml +++ b/core/modules/system/tests/modules/entity_test/entity_test.routing.yml @@ -60,5 +60,22 @@ entity.entity_test.list_referencing_entities: requirements: _access: 'TRUE' +entity.entity_test.list_labels_alphabetically: + path: '/entity_test/list_labels_alphabetically/{entity_type_id}' + defaults: + _content: '\Drupal\entity_test\Controller\EntityTestController::listEntitiesAlphabetically' + _title: 'List labels of entities of the given entity type alphabetically' + requirements: + _access: 'TRUE' + +entity.entity_test.list_empty: + path: '/entity_test/list_empty/{entity_type_id}' + defaults: + _content: '\Drupal\entity_test\Controller\EntityTestController::listEntitiesEmpty' + _title: 'Empty list of entities of the given entity type, empty because no entities match the query' + requirements: + _access: 'TRUE' + + route_callbacks: - '\Drupal\entity_test\Routing\EntityTestRoutes::routes' diff --git a/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php b/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php index a9d114a..8e4d63d 100644 --- a/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php +++ b/core/modules/system/tests/modules/entity_test/src/Controller/EntityTestController.php @@ -7,6 +7,7 @@ namespace Drupal\entity_test\Controller; +use Drupal\Core\Cache\Cache; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Entity\Query\QueryFactory; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -121,4 +122,74 @@ public function listReferencingEntities($entity_reference_field_name, $reference ->viewMultiple($entities, 'full'); } + /** + * List entities of the given entity type labels, sorted alphabetically. + * + * @param string $entity_type_id + * The type of the entity being listed. + * + * @return array + * A renderable array. + */ + public function listEntitiesAlphabetically($entity_type_id) { + $entity_type_definition = $this->entityManager()->getDefinition($entity_type_id); + $query = $this->entityQueryFactory->get($entity_type_id); + + // Sort by label field, if any. + if ($label_field = $entity_type_definition->getKey('label')) { + $query->sort($label_field); + } + + $entities = $this->entityManager() + ->getStorage($entity_type_id) + ->loadMultiple($query->execute()); + + $class = $entity_type_definition->getClass(); + + $cache_tags = []; + $labels = []; + foreach ($entities as $entity) { + $labels[] = $entity->label(); + $cache_tags = Cache::mergeTags($cache_tags, $entity->getCacheTag()); + } + // Always associate the list cache tag, otherwise the cached empty result + // wouldn't be invalidated. This would continue to show nothing matches the + // query, even though a newly created entity might match the query. + $cache_tags = Cache::mergeTags($cache_tags, $class::getListCacheTags()); + + return [ + '#theme' => 'item_list', + '#items' => $labels, + '#title' => $entity_type_id . ' entities', + '#cache' => [ + 'tags' => $cache_tags, + ], + ]; + } + + + /** + * Empty list of entities of the given entity type. + * + * Empty because no entities match the query. That may seem contrived, but it + * is an excellent way for testing whether an entity's list cache tags are + * working as expected. + * + * @param string $entity_type_id + * The type of the entity being listed. + * + * @return array + * A renderable array. + */ + public function listEntitiesEmpty($entity_type_id) { + $class = $this->entityManager()->getDefinition($entity_type_id)->getClass(); + return [ + '#theme' => 'item_list', + '#items' => [], + '#cache' => [ + 'tags' => $class::getListCacheTags(), + ], + ]; + } + } diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module index 51a3e8b..23838bc 100644 --- a/core/modules/toolbar/toolbar.module +++ b/core/modules/toolbar/toolbar.module @@ -13,8 +13,7 @@ use Drupal\Component\Datetime\DateTimePlus; use Drupal\Component\Utility\Crypt; use Drupal\Component\Utility\String; -use Drupal\user\RoleInterface; -use Drupal\user\UserInterface; +use Drupal\user\Entity\Role; /** * Implements hook_help(). @@ -371,7 +370,7 @@ function _toolbar_get_subtrees_hash($langcode) { // caches later, based on the user's ID regardless of language. // Clear the cache when the 'locale' tag is deleted. This ensures a fresh // subtrees rendering when string translations are made. - \Drupal::cache('toolbar')->set($cid, $hash, Cache::PERMANENT, array('user:' . $uid, 'locale', 'menu:admin', 'user_roles')); + \Drupal::cache('toolbar')->set($cid, $hash, Cache::PERMANENT, array('user:' . $uid, 'locale', 'menu:admin', Role::getListCacheTags())); } return $hash; } diff --git a/core/modules/user/src/Tests/UserCacheTagsTest.php b/core/modules/user/src/Tests/UserCacheTagsTest.php index d3ac294..4085de4 100644 --- a/core/modules/user/src/Tests/UserCacheTagsTest.php +++ b/core/modules/user/src/Tests/UserCacheTagsTest.php @@ -48,4 +48,11 @@ protected function createEntity() { return $user; } + /** + * {@inheritdoc} + */ + protected function getAdditionalCacheTagsForEntityListing() { + return ['user:0', 'user:1']; + } + } diff --git a/core/modules/user/src/Tests/UserPictureTest.php b/core/modules/user/src/Tests/UserPictureTest.php index d20ade7..639c382 100644 --- a/core/modules/user/src/Tests/UserPictureTest.php +++ b/core/modules/user/src/Tests/UserPictureTest.php @@ -7,6 +7,7 @@ namespace Drupal\user\Tests; +use Drupal\Core\Cache\Cache; use Drupal\simpletest\WebTestBase; /** @@ -102,6 +103,9 @@ function testPictureOnNodeComment() { ->set('features.comment_user_picture', TRUE) ->save(); + // @todo Remove when https://www.drupal.org/node/2040135 lands. + Cache::invalidateTags(['rendered']); + $edit = array( 'comment_body[0][value]' => $this->randomString(), ); @@ -113,7 +117,9 @@ function testPictureOnNodeComment() { ->set('features.node_user_picture', FALSE) ->set('features.comment_user_picture', FALSE) ->save(); - \Drupal::entityManager()->getViewBuilder('comment')->resetCache(); + + // @todo Remove when https://www.drupal.org/node/2040135 lands. + Cache::invalidateTags(['rendered']); $this->drupalGet('node/' . $node->id()); $this->assertNoRaw(file_uri_target($file->getFileUri()), 'User picture not found on node and comment.'); diff --git a/core/modules/views/src/Plugin/views/cache/CachePluginBase.php b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php index a462641..27f0733 100644 --- a/core/modules/views/src/Plugin/views/cache/CachePluginBase.php +++ b/core/modules/views/src/Plugin/views/cache/CachePluginBase.php @@ -358,9 +358,10 @@ protected function getCacheTags() { $entity_information = $this->view->query->getEntityTableInfo(); if (!empty($entity_information)) { - // Add an ENTITY_TYPE_list tag for each entity type used by this view. + // Add the list cache tags for each entity type used by this view. foreach (array_keys($entity_information) as $entity_type) { - $tags[] = $entity_type . '_list'; + $class = \Drupal::entityManager()->getDefinition($entity_type)->getClass(); + $tags = Cache::mergeTags($tags, $class::getListCacheTags()); } } diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php index 7e1bc27..8e2bb49 100644 --- a/core/modules/views_ui/src/ViewUI.php +++ b/core/modules/views_ui/src/ViewUI.php @@ -1123,8 +1123,8 @@ public function getCacheTag() { /** * {@inheritdoc} */ - public function getListCacheTags() { - $this->storage->getListCacheTags(); + public static function getListCacheTags() { + return View::getListCacheTags(); } } diff --git a/core/modules/views_ui/tests/src/Unit/ViewUIObjectTest.php b/core/modules/views_ui/tests/src/Unit/ViewUIObjectTest.php index c31306a..7817b24 100644 --- a/core/modules/views_ui/tests/src/Unit/ViewUIObjectTest.php +++ b/core/modules/views_ui/tests/src/Unit/ViewUIObjectTest.php @@ -42,7 +42,7 @@ public function testEntityDecoration() { // process. ConfigEntityInterface::getConfigDependencyName() and // ConfigEntityInterface::calculateDependencies() are only used for // dependency management. - if (!in_array($reflection_method->getName(), ['isNew', 'isSyncing', 'isUninstalling', 'getConfigDependencyName', 'calculateDependencies'])) { + if (!in_array($reflection_method->getName(), ['isNew', 'isSyncing', 'isUninstalling', 'getConfigDependencyName', 'calculateDependencies', 'getListCacheTags'])) { if (count($reflection_method->getParameters()) == 0) { $method_args[$reflection_method->getName()] = array(); } diff --git a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php index 6bf678f..ff3c574 100644 --- a/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Config/Entity/ConfigEntityStorageTest.php @@ -121,6 +121,9 @@ protected function setUp() { $this->entityType->expects($this->any()) ->method('getClass') ->will($this->returnValue(get_class($this->getMockEntity()))); + $this->entityType->expects($this->any()) + ->method('getBundleEntityType') + ->will($this->returnValue('bundle')); $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); @@ -150,6 +153,9 @@ protected function setUp() { ->method('getDefinition') ->with('test_entity_type') ->will($this->returnValue($this->entityType)); + $this->entityManager->expects($this->any()) + ->method('getEntityTypeFromClass') + ->will($this->returnValue('test_entity_type')); $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); @@ -241,7 +247,7 @@ public function testSaveInsert(EntityInterface $entity) { $this->cacheBackend->expects($this->once()) ->method('invalidateTags') ->with(array( - $this->entityTypeId . 's', // List cache tag. + $this->entityTypeId . '_list', // List cache tag. )); $this->configFactory->expects($this->exactly(2)) @@ -301,7 +307,7 @@ public function testSaveUpdate(EntityInterface $entity) { ->method('invalidateTags') ->with(array( $this->entityTypeId . ':foo', // Own cache tag. - $this->entityTypeId . 's', // List cache tag. + $this->entityTypeId . '_list', // List cache tag. )); $this->configFactory->expects($this->exactly(2)) @@ -361,7 +367,7 @@ public function testSaveRename(ConfigEntityInterface $entity) { ->method('invalidateTags') ->with(array( $this->entityTypeId .':bar', // Own cache tag. - $this->entityTypeId . 's', // List cache tag. + $this->entityTypeId . '_list', // List cache tag. )); $this->configFactory->expects($this->once()) @@ -493,7 +499,7 @@ public function testSaveNoMismatch() { $this->cacheBackend->expects($this->once()) ->method('invalidateTags') ->with(array( - $this->entityTypeId . 's', // List cache tag. + $this->entityTypeId . '_list', // List cache tag. )); $this->configFactory->expects($this->once()) @@ -728,7 +734,7 @@ public function testDelete() { ->with(array( $this->entityTypeId . ':bar', // Own cache tag. $this->entityTypeId . ':foo', // Own cache tag. - $this->entityTypeId . 's', // List cache tag. + $this->entityTypeId . '_list', // List cache tag. )); $this->configFactory->expects($this->exactly(2)) @@ -790,7 +796,6 @@ public function testDeleteNothing() { * @return \Drupal\Core\Entity\EntityInterface|\PHPUnit_Framework_MockObject_MockObject */ public function getMockEntity(array $values = array(), $methods = array()) { - $methods[] = 'onSaveOrDelete'; $methods[] = 'onUpdateBundleEntity'; return $this->getMockForAbstractClass('Drupal\Core\Config\Entity\ConfigEntityBase', array($values, 'test_entity_type'), '', TRUE, TRUE, TRUE, $methods); } diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php index 1fc8d7b..684024b 100644 --- a/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php @@ -98,6 +98,9 @@ protected function setUp() { $this->entityTypeId = $this->randomMachineName(); $this->entityType = $this->getMock('\Drupal\Core\Entity\EntityTypeInterface'); + $this->entityType->expects($this->any()) + ->method('getBundleEntityType') + ->will($this->returnValue($this->entityTypeId)); $this->entityManager = $this->getMock('\Drupal\Core\Entity\EntityManagerInterface'); $this->entityManager->expects($this->any()) @@ -248,6 +251,10 @@ function setupTestLoad() { ->disableOriginalConstructor() ->setMethods($methods) ->getMock(); + + $this->entityManager->expects($this->any()) + ->method('getEntityTypeFromClass') + ->will($this->returnValue($this->entityTypeId)); } /** @@ -389,16 +396,20 @@ public function testPreSave() { * @covers ::postSave */ public function testPostSave() { + $this->entityManager->expects($this->any()) + ->method('getEntityTypeFromClass') + ->will($this->returnValue($this->entityTypeId)); + $this->cacheBackend->expects($this->at(0)) ->method('invalidateTags') ->with(array( - $this->entityTypeId . 's', // List cache tag. + $this->entityTypeId . '_list', // List cache tag. )); $this->cacheBackend->expects($this->at(1)) ->method('invalidateTags') ->with(array( $this->entityTypeId . ':' . $this->values['id'], // Own cache tag. - $this->entityTypeId . 's', // List cache tag. + $this->entityTypeId . '_list', // List cache tag. )); // This method is internal, so check for errors on calling it only. @@ -446,22 +457,19 @@ public function testPreDelete() { * @covers ::postDelete */ public function testPostDelete() { + $this->entityManager->expects($this->any()) + ->method('getEntityTypeFromClass') + ->will($this->returnValue($this->entityTypeId)); + $this->cacheBackend->expects($this->once()) ->method('invalidateTags') ->with(array( $this->entityTypeId . ':' . $this->values['id'], - $this->entityTypeId . 's', + $this->entityTypeId . '_list', )); $storage = $this->getMock('\Drupal\Core\Entity\EntityStorageInterface'); - $entity = $this->getMockBuilder('\Drupal\Core\Entity\Entity') - ->setConstructorArgs(array($this->values, $this->entityTypeId)) - ->setMethods(array('onSaveOrDelete')) - ->getMock(); - $entity->expects($this->once()) - ->method('onSaveOrDelete'); - - $entities = array($this->values['id'] => $entity); + $entities = array($this->values['id'] => $this->entity); $this->entity->postDelete($storage, $entities); } diff --git a/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php index 5c69511..21e762e 100644 --- a/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php +++ b/core/tests/Drupal/Tests/Core/Entity/KeyValueStore/KeyValueEntityStorageTest.php @@ -426,7 +426,6 @@ public function testSaveContentEntity() { $this->keyValueStore->expects($this->never()) ->method('delete'); $entity = $this->getMockEntity('Drupal\Core\Entity\ContentEntityBase', array(), array( - 'onSaveOrDelete', 'toArray', 'id', ));