Commit a78a1063 authored by catch's avatar catch

Issue #2241235 by Wim Leers: Shortcut/ShortcutSet entity types should use cache tags.

parent 7bc79206
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
* Allows users to manage customizable lists of shortcut links. * Allows users to manage customizable lists of shortcut links.
*/ */
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\UrlHelper; use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Routing\UrlMatcher; use Drupal\Core\Routing\UrlMatcher;
use Drupal\Core\Url; use Drupal\Core\Url;
...@@ -299,12 +300,15 @@ function shortcut_renderable_links($shortcut_set = NULL) { ...@@ -299,12 +300,15 @@ function shortcut_renderable_links($shortcut_set = NULL) {
$shortcut_set = shortcut_current_displayed_set(); $shortcut_set = shortcut_current_displayed_set();
} }
/** @var \Drupal\shortcut\ShortcutInterface[] $shortcuts */
$shortcuts = \Drupal::entityManager()->getStorage('shortcut')->loadByProperties(array('shortcut_set' => $shortcut_set->id())); $shortcuts = \Drupal::entityManager()->getStorage('shortcut')->loadByProperties(array('shortcut_set' => $shortcut_set->id()));
$all_cache_tags = array();
foreach ($shortcuts as $shortcut) { foreach ($shortcuts as $shortcut) {
$links[] = array( $links[] = array(
'title' => $shortcut->label(), 'title' => $shortcut->label(),
'href' => $shortcut->path->value, 'href' => $shortcut->path->value,
); );
$all_cache_tags[] = $shortcut->getCacheTag();
} }
if (!empty($links)) { if (!empty($links)) {
...@@ -314,6 +318,9 @@ function shortcut_renderable_links($shortcut_set = NULL) { ...@@ -314,6 +318,9 @@ function shortcut_renderable_links($shortcut_set = NULL) {
'#attributes' => array( '#attributes' => array(
'class' => array('menu'), 'class' => array('menu'),
), ),
'#cache' => array(
'tags' => NestedArray::mergeDeepArray($all_cache_tags),
),
); );
} }
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
namespace Drupal\shortcut\Entity; namespace Drupal\shortcut\Entity;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\ContentEntityBase; use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityStorageInterface; use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface; use Drupal\Core\Entity\EntityTypeInterface;
...@@ -25,6 +26,7 @@ ...@@ -25,6 +26,7 @@
* "form" = { * "form" = {
* "default" = "Drupal\shortcut\ShortcutForm", * "default" = "Drupal\shortcut\ShortcutForm",
* "add" = "Drupal\shortcut\ShortcutForm", * "add" = "Drupal\shortcut\ShortcutForm",
* "edit" = "Drupal\shortcut\ShortcutForm",
* "delete" = "Drupal\shortcut\Form\ShortcutDeleteForm" * "delete" = "Drupal\shortcut\Form\ShortcutDeleteForm"
* }, * },
* "translation" = "Drupal\content_translation\ContentTranslationHandler" * "translation" = "Drupal\content_translation\ContentTranslationHandler"
...@@ -39,9 +41,12 @@ ...@@ -39,9 +41,12 @@
* "label" = "title" * "label" = "title"
* }, * },
* links = { * links = {
* "canonical" = "shortcut.link_edit",
* "delete-form" = "shortcut.link_delete", * "delete-form" = "shortcut.link_delete",
* "edit-form" = "shortcut.link_edit" * "edit-form" = "shortcut.link_edit",
* } * "admin-form" = "shortcut.link_edit"
* },
* bundle_entity_type = "shortcut_set"
* ) * )
*/ */
class Shortcut extends ContentEntityBase implements ShortcutInterface { class Shortcut extends ContentEntityBase implements ShortcutInterface {
...@@ -129,6 +134,21 @@ public function preSave(EntityStorageInterface $storage) { ...@@ -129,6 +134,21 @@ public function preSave(EntityStorageInterface $storage) {
$this->setRouteParams($url->getRouteParameters()); $this->setRouteParams($url->getRouteParameters());
} }
/**
* {@inheritdoc}
*/
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
parent::postSave($storage, $update);
// Entity::postSave() calls Entity::invalidateTagsOnSave(), which only
// handles the regular cases. The Shortcut entity has one special case: a
// newly created shortcut is *also* added to a shortcut set, so we must
// invalidate the associated shortcut set's cache tag.
if (!$update) {
Cache::invalidateTags($this->getCacheTag());
}
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
...@@ -199,4 +219,18 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ...@@ -199,4 +219,18 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
return $fields; return $fields;
} }
/**
* {@inheritdoc}
*/
public function getCacheTag() {
return $this->shortcut_set->entity->getCacheTag();
}
/**
* {@inheritdoc}
*/
public function getListCacheTags() {
return $this->shortcut_set->entity->getListCacheTags();
}
} }
...@@ -60,11 +60,11 @@ class ShortcutSet extends ConfigEntityBase implements ShortcutSetInterface { ...@@ -60,11 +60,11 @@ class ShortcutSet extends ConfigEntityBase implements ShortcutSetInterface {
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function postCreate(EntityStorageInterface $storage) { public function postSave(EntityStorageInterface $storage, $update = TRUE) {
parent::postCreate($storage); parent::postSave($storage, $update);
// Generate menu-compatible set name. // Generate menu-compatible set name.
if (!$this->getOriginalId()) { if (!$update && !$this->getOriginalId()) {
// Save a new shortcut set with links copied from the user's default set. // Save a new shortcut set with links copied from the user's default set.
$default_set = shortcut_default_set(); $default_set = shortcut_default_set();
foreach ($default_set->getShortcuts() as $shortcut) { foreach ($default_set->getShortcuts() as $shortcut) {
......
<?php
/**
* @file
* Contains \Drupal\shortcut\Tests\ShortcutCacheTagsTest.
*/
namespace Drupal\shortcut\Tests;
use Drupal\system\Tests\Entity\EntityCacheTagsTestBase;
/**
* Tests the Shortcut entity's cache tags.
*/
class ShortcutCacheTagsTest extends EntityCacheTagsTestBase {
/**
* {@inheritdoc}
*/
public static $modules = array('shortcut');
/**
* {@inheritdoc}
*/
public static function getInfo() {
return parent::generateStandardizedInfo('Shortcut link', 'Shortcut');
}
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
// Give anonymous users permission to customize shortcut links, so that we
// can verify the cache tags of cached versions of shortcuts.
$user_role = entity_load('user_role', DRUPAL_ANONYMOUS_RID);
$user_role->grantPermission('customize shortcut links');
$user_role->save();
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
// Create a "Llama" shortcut.
$shortcut = entity_create('shortcut', array(
'set' => 'default',
'title' => t('Llama'),
'weight' => 0,
'path' => 'admin',
));
$shortcut->save();
return $shortcut;
}
/**
* Tests that when creating a shortcut, the shortcut set tag is invalidated.
*/
public function testEntityCreation() {
// Create a cache entry that is tagged with a shortcut set cache tag.
$cache_tags = array('shortcut_set' => 'default');
\Drupal::cache('render')->set('foo', 'bar', \Drupal\Core\Cache\CacheBackendInterface::CACHE_PERMANENT, $cache_tags);
// Verify a cache hit.
$this->verifyRenderCache('foo', array('shortcut_set:default'));
// Now create a shortcut entity in that shortcut set.
$this->createEntity();
// Verify a cache miss.
$this->assertFalse(\Drupal::cache('render')->get('foo'), 'Creating a new shortcut invalidates the cache tag of the shortcut set.');
}
}
...@@ -7,9 +7,10 @@ ...@@ -7,9 +7,10 @@
namespace Drupal\system\Tests\Entity; namespace Drupal\system\Tests\Entity;
use Drupal\Component\Utility\String; use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Cache\Cache; use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\EventSubscriber\HtmlViewSubscriber;
use Drupal\Core\Field\FieldDefinitionInterface; use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\system\Tests\Cache\PageCacheTagsTestBase; use Drupal\system\Tests\Cache\PageCacheTagsTestBase;
...@@ -182,8 +183,12 @@ protected function createReferenceTestEntities($referenced_entity) { ...@@ -182,8 +183,12 @@ protected function createReferenceTestEntities($referenced_entity) {
), ),
), ),
))->save(); ))->save();
$formatter = 'entity_reference_entity_view';
if (!$this->entity->getEntityType()->hasControllerClass('view_builder')) {
$formatter = 'entity_reference_label';
}
entity_get_display($entity_type, $bundle, 'full') entity_get_display($entity_type, $bundle, 'full')
->setComponent($field_name, array('type' => 'entity_reference_entity_view')) ->setComponent($field_name, array('type' => $formatter))
->save(); ->save();
// Create an entity that does reference the entity being tested. // Create an entity that does reference the entity being tested.
...@@ -227,23 +232,27 @@ public function testReferencedEntity() { ...@@ -227,23 +232,27 @@ public function testReferencedEntity() {
$theme_cache_tags = array('content:1', 'theme:stark', 'theme_global_settings:1'); $theme_cache_tags = array('content:1', 'theme:stark', 'theme_global_settings:1');
// Generate the standardized entity cache tags. $view_cache_tag = array();
$cache_tag = $entity_type . ':' . $this->entity->id(); if ($this->entity->getEntityType()->hasControllerClass('view_builder')) {
$view_cache_tag = $entity_type . '_view:1'; $view_cache_tag = \Drupal::entityManager()->getViewBuilder($entity_type)
->getCacheTag();
}
// Generate the cache tags for the (non) referencing entities. // Generate the cache tags for the (non) referencing entities.
$referencing_entity_cache_tags = array( $referencing_entity_cache_tags = NestedArray::mergeDeep(
'entity_test_view:1', $this->referencing_entity->getCacheTag(),
'entity_test:' . $this->referencing_entity->id(), \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTag(),
// Includes the main entity's cache tags, since this entity references it. // Includes the main entity's cache tags, since this entity references it.
$view_cache_tag, $this->entity->getCacheTag(),
$cache_tag, $view_cache_tag
); );
$referencing_entity_cache_tags = explode(' ', HtmlViewSubscriber::convertCacheTagsToHeader($referencing_entity_cache_tags));
$referencing_entity_cache_tags = array_merge($referencing_entity_cache_tags, $this->getAdditionalCacheTagsForEntity($this->entity)); $referencing_entity_cache_tags = array_merge($referencing_entity_cache_tags, $this->getAdditionalCacheTagsForEntity($this->entity));
$non_referencing_entity_cache_tags = array( $non_referencing_entity_cache_tags = NestedArray::mergeDeep(
'entity_test_view:1', $this->non_referencing_entity->getCacheTag(),
'entity_test:' . $this->non_referencing_entity->id(), \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTag()
); );
$non_referencing_entity_cache_tags = explode(' ', HtmlViewSubscriber::convertCacheTagsToHeader($non_referencing_entity_cache_tags));
// Prime the page cache for the referencing entity. // Prime the page cache for the referencing entity.
...@@ -255,9 +264,7 @@ public function testReferencedEntity() { ...@@ -255,9 +264,7 @@ public function testReferencedEntity() {
// Also verify the existence of an entity render cache entry. // Also verify the existence of an entity render cache entry.
$cid = 'entity_view:entity_test:' . $this->referencing_entity->id() . ':full:stark:r.anonymous'; $cid = 'entity_view:entity_test:' . $this->referencing_entity->id() . ':full:stark:r.anonymous';
$cache_entry = \Drupal::cache('render')->get($cid); $this->verifyRenderCache($cid, $referencing_entity_cache_tags);
$this->assertIdentical($cache_entry->tags, $referencing_entity_cache_tags);
// Prime the page cache for the non-referencing entity. // Prime the page cache for the non-referencing entity.
$this->verifyPageCache($non_referencing_entity_path, 'MISS'); $this->verifyPageCache($non_referencing_entity_path, 'MISS');
...@@ -268,8 +275,7 @@ public function testReferencedEntity() { ...@@ -268,8 +275,7 @@ public function testReferencedEntity() {
// Also verify the existence of an entity render cache entry. // Also verify the existence of an entity render cache entry.
$cid = 'entity_view:entity_test:' . $this->non_referencing_entity->id() . ':full:stark:r.anonymous'; $cid = 'entity_view:entity_test:' . $this->non_referencing_entity->id() . ':full:stark:r.anonymous';
$cache_entry = \Drupal::cache('render')->get($cid); $this->verifyRenderCache($cid, $non_referencing_entity_cache_tags);
$this->assertIdentical($cache_entry->tags, $non_referencing_entity_cache_tags);
...@@ -322,19 +328,21 @@ public function testReferencedEntity() { ...@@ -322,19 +328,21 @@ public function testReferencedEntity() {
$this->verifyPageCache($non_referencing_entity_path, 'HIT'); $this->verifyPageCache($non_referencing_entity_path, 'HIT');
// Verify that after modifying the entity's "full" display, there is a cache if ($this->entity->getEntityType()->hasControllerClass('view_builder')) {
// miss for both the referencing entity, and the listing of referencing // Verify that after modifying the entity's "full" display, there is a cache
// entities, but not for the non-referencing entity. // miss for both the referencing entity, and the listing of referencing
$this->pass("Test modification of referenced entity's 'full' display.", 'Debug'); // entities, but not for the non-referencing entity.
$entity_display = entity_get_display($entity_type, $this->entity->bundle(), 'full'); $this->pass("Test modification of referenced entity's 'full' display.", 'Debug');
$entity_display->save(); $entity_display = entity_get_display($entity_type, $this->entity->bundle(), 'full');
$this->verifyPageCache($referencing_entity_path, 'MISS'); $entity_display->save();
$this->verifyPageCache($listing_path, 'MISS'); $this->verifyPageCache($referencing_entity_path, 'MISS');
$this->verifyPageCache($non_referencing_entity_path, 'HIT'); $this->verifyPageCache($listing_path, 'MISS');
$this->verifyPageCache($non_referencing_entity_path, 'HIT');
// Verify cache hits. // Verify cache hits.
$this->verifyPageCache($referencing_entity_path, 'HIT'); $this->verifyPageCache($referencing_entity_path, 'HIT');
$this->verifyPageCache($listing_path, 'HIT'); $this->verifyPageCache($listing_path, 'HIT');
}
$bundle_entity_type = $this->entity->getEntityType()->getBundleEntityType(); $bundle_entity_type = $this->entity->getEntityType()->getBundleEntityType();
...@@ -391,7 +399,7 @@ public function testReferencedEntity() { ...@@ -391,7 +399,7 @@ public function testReferencedEntity() {
// a cache miss for both the referencing entity, and the listing of // 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 the non-referencing entity.
$this->pass("Test invalidation of referenced entity's cache tag.", 'Debug'); $this->pass("Test invalidation of referenced entity's cache tag.", 'Debug');
Cache::invalidateTags(array($entity_type => array($this->entity->id()))); Cache::invalidateTags($this->entity->getCacheTag());
$this->verifyPageCache($referencing_entity_path, 'MISS'); $this->verifyPageCache($referencing_entity_path, 'MISS');
$this->verifyPageCache($listing_path, 'MISS'); $this->verifyPageCache($listing_path, 'MISS');
$this->verifyPageCache($non_referencing_entity_path, 'HIT'); $this->verifyPageCache($non_referencing_entity_path, 'HIT');
...@@ -401,19 +409,20 @@ public function testReferencedEntity() { ...@@ -401,19 +409,20 @@ public function testReferencedEntity() {
$this->verifyPageCache($listing_path, 'HIT'); $this->verifyPageCache($listing_path, 'HIT');
// Verify that after invalidating the generic entity type's view cache tag if (!empty($view_cache_tag)) {
// directly, there is a cache miss for both the referencing entity, and the // Verify that after invalidating the generic entity type's view cache tag
// listing of referencing entities, but not for the non-referencing entity. // directly, there is a cache miss for both the referencing entity, and the
$this->pass("Test invalidation of referenced entity's 'view' cache tag.", 'Debug'); // listing of referencing entities, but not for the non-referencing entity.
Cache::invalidateTags(array($entity_type . '_view' => TRUE)); $this->pass("Test invalidation of referenced entity's 'view' cache tag.", 'Debug');
$this->verifyPageCache($referencing_entity_path, 'MISS'); Cache::invalidateTags($view_cache_tag);
$this->verifyPageCache($listing_path, 'MISS'); $this->verifyPageCache($referencing_entity_path, 'MISS');
$this->verifyPageCache($non_referencing_entity_path, 'HIT'); $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 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 // 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 // referencing entity, and the listing of referencing entities, but not for
...@@ -425,12 +434,31 @@ public function testReferencedEntity() { ...@@ -425,12 +434,31 @@ public function testReferencedEntity() {
$this->verifyPageCache($non_referencing_entity_path, 'HIT'); $this->verifyPageCache($non_referencing_entity_path, 'HIT');
// Verify cache hits. // Verify cache hits.
$tags = array_merge($theme_cache_tags, array( $referencing_entity_cache_tags = NestedArray::mergeDeep(
'entity_test_view:1', $this->referencing_entity->getCacheTag(),
'entity_test:' . $this->referencing_entity->id(), \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTag()
)); );
$referencing_entity_cache_tags = explode(' ', HtmlViewSubscriber::convertCacheTagsToHeader($referencing_entity_cache_tags));
$tags = array_merge($theme_cache_tags, $referencing_entity_cache_tags);
$this->verifyPageCache($referencing_entity_path, 'HIT', $tags); $this->verifyPageCache($referencing_entity_path, 'HIT', $tags);
$this->verifyPageCache($listing_path, 'HIT', $theme_cache_tags); $this->verifyPageCache($listing_path, 'HIT', $theme_cache_tags);
} }
/**
* Verify that a given render cache entry exists, with the correct cache tags.
*
* @param string $cid
* The render cache item ID.
* @param array $tags
* An array of expected cache tags.
*/
protected function verifyRenderCache($cid, array $tags) {
// Also verify the existence of an entity render cache entry.
$cache_entry = \Drupal::cache('render')->get($cid);
$this->assertTrue($cache_entry, 'A render cache entry exists.');
sort($cache_entry->tags);
sort($tags);
$this->assertIdentical($cache_entry->tags, $tags);
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment