Issue #2145751 by jibran, yongt9412, DuaelFr, Berdir, pnagornyak, arpad.rozsa, mbovan, Wim Leers, Nixou, effulgentsia, Fabianx, kristiaanvandeneynde, anavarre, dawehner: Introduce ENTITY_TYPE_list:BUNDLE cache tag and add it to single bundle listing
(cherry picked from commit e68ca9358f
)
merge-requests/64/head
parent
43b42be33c
commit
a7eacc877e
|
@ -490,7 +490,7 @@ abstract class ConfigEntityBase extends EntityBase implements ConfigEntityInterf
|
||||||
* already invalidates it.
|
* already invalidates it.
|
||||||
*/
|
*/
|
||||||
protected function invalidateTagsOnSave($update) {
|
protected function invalidateTagsOnSave($update) {
|
||||||
Cache::invalidateTags($this->getEntityType()->getListCacheTags());
|
Cache::invalidateTags($this->getListCacheTagsToInvalidate());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -500,7 +500,11 @@ abstract class ConfigEntityBase extends EntityBase implements ConfigEntityInterf
|
||||||
* config system already invalidates them.
|
* config system already invalidates them.
|
||||||
*/
|
*/
|
||||||
protected static function invalidateTagsOnDelete(EntityTypeInterface $entity_type, array $entities) {
|
protected static function invalidateTagsOnDelete(EntityTypeInterface $entity_type, array $entities) {
|
||||||
Cache::invalidateTags($entity_type->getListCacheTags());
|
$tags = $entity_type->getListCacheTags();
|
||||||
|
foreach ($entities as $entity) {
|
||||||
|
$tags = Cache::mergeTags($tags, $entity->getListCacheTagsToInvalidate());
|
||||||
|
}
|
||||||
|
Cache::invalidateTags($tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -493,12 +493,24 @@ abstract class EntityBase implements EntityInterface {
|
||||||
return $this->cacheContexts;
|
return $this->cacheContexts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list cache tags to invalidate for this entity.
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
* Set of list cache tags.
|
||||||
|
*/
|
||||||
|
protected function getListCacheTagsToInvalidate() {
|
||||||
|
$tags = $this->getEntityType()->getListCacheTags();
|
||||||
|
if ($this->getEntityType()->hasKey('bundle')) {
|
||||||
|
$tags[] = $this->getEntityTypeId() . '_list:' . $this->bundle();
|
||||||
|
}
|
||||||
|
return $tags;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public function getCacheTagsToInvalidate() {
|
public function getCacheTagsToInvalidate() {
|
||||||
// @todo Add bundle-specific listing cache tag?
|
|
||||||
// https://www.drupal.org/node/2145751
|
|
||||||
if ($this->isNew()) {
|
if ($this->isNew()) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -563,7 +575,7 @@ abstract class EntityBase implements EntityInterface {
|
||||||
// updated entity may start to appear in a listing because it now meets that
|
// 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
|
// listing's filtering requirements. A newly created entity may start to
|
||||||
// appear in listings because it did not exist before.)
|
// appear in listings because it did not exist before.)
|
||||||
$tags = $this->getEntityType()->getListCacheTags();
|
$tags = $this->getListCacheTagsToInvalidate();
|
||||||
if ($this->hasLinkTemplate('canonical')) {
|
if ($this->hasLinkTemplate('canonical')) {
|
||||||
// Creating or updating an entity may change a cached 403 or 404 response.
|
// Creating or updating an entity may change a cached 403 or 404 response.
|
||||||
$tags = Cache::mergeTags($tags, ['4xx-response']);
|
$tags = Cache::mergeTags($tags, ['4xx-response']);
|
||||||
|
@ -592,6 +604,7 @@ abstract class EntityBase implements EntityInterface {
|
||||||
// cache tag, but subsequent list pages would not be invalidated, hence we
|
// cache tag, but subsequent list pages would not be invalidated, hence we
|
||||||
// must invalidate its list cache tags as well.)
|
// must invalidate its list cache tags as well.)
|
||||||
$tags = Cache::mergeTags($tags, $entity->getCacheTagsToInvalidate());
|
$tags = Cache::mergeTags($tags, $entity->getCacheTagsToInvalidate());
|
||||||
|
$tags = Cache::mergeTags($tags, $entity->getListCacheTagsToInvalidate());
|
||||||
}
|
}
|
||||||
Cache::invalidateTags($tags);
|
Cache::invalidateTags($tags);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,3 +4,10 @@ cache_test.url_bubbling:
|
||||||
_controller: '\Drupal\cache_test\Controller\CacheTestController::urlBubbling'
|
_controller: '\Drupal\cache_test\Controller\CacheTestController::urlBubbling'
|
||||||
requirements:
|
requirements:
|
||||||
_access: 'TRUE'
|
_access: 'TRUE'
|
||||||
|
|
||||||
|
cache_test_list.bundle_tags:
|
||||||
|
path: '/cache-test-list/{entity_type_id}/{bundle}'
|
||||||
|
defaults:
|
||||||
|
_controller: '\Drupal\cache_test\Controller\CacheTestController::bundleTags'
|
||||||
|
requirements:
|
||||||
|
_access: 'TRUE'
|
||||||
|
|
|
@ -19,4 +19,30 @@ class CacheTestController {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bundle listing tags invalidation.
|
||||||
|
*
|
||||||
|
* @param string $entity_type_id
|
||||||
|
* The entity type ID.
|
||||||
|
* @param string $bundle
|
||||||
|
* The bundle.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
* Renderable array.
|
||||||
|
*/
|
||||||
|
public function bundleTags($entity_type_id, $bundle) {
|
||||||
|
$storage = \Drupal::entityTypeManager()->getStorage($entity_type_id);
|
||||||
|
$entity_ids = $storage->getQuery()->condition('type', $bundle)->execute();
|
||||||
|
$page = [];
|
||||||
|
|
||||||
|
$entities = $storage->loadMultiple($entity_ids);
|
||||||
|
foreach ($entities as $entity) {
|
||||||
|
$page[$entity->id()] = [
|
||||||
|
'#markup' => $entity->label(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
$page['#cache']['tags'] = [$entity_type_id . '_list:' . $bundle];
|
||||||
|
return $page;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\FunctionalTests\Entity;
|
||||||
|
|
||||||
|
use Drupal\Core\Url;
|
||||||
|
use Drupal\entity_test\Entity\EntityTestBundle;
|
||||||
|
use Drupal\entity_test\Entity\EntityTestWithBundle;
|
||||||
|
use Drupal\Tests\BrowserTestBase;
|
||||||
|
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that bundle tags are invalidated when entities change.
|
||||||
|
*
|
||||||
|
* @group Entity
|
||||||
|
*/
|
||||||
|
class EntityBundleListCacheTest extends BrowserTestBase {
|
||||||
|
|
||||||
|
use AssertPageCacheContextsAndTagsTrait;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modules to enable.
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public static $modules = ['cache_test', 'entity_test'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected $defaultTheme = 'classy';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected function setUp() {
|
||||||
|
parent::setUp();
|
||||||
|
EntityTestBundle::create([
|
||||||
|
'id' => 'bundle_a',
|
||||||
|
'label' => 'Bundle A',
|
||||||
|
])->save();
|
||||||
|
EntityTestBundle::create([
|
||||||
|
'id' => 'bundle_b',
|
||||||
|
'label' => 'Bundle B',
|
||||||
|
])->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that tags are invalidated when an entity with that bundle changes.
|
||||||
|
*/
|
||||||
|
public function testBundleListingCache() {
|
||||||
|
// Access to lists of test entities with each bundle.
|
||||||
|
$bundle_a_url = Url::fromRoute('cache_test_list.bundle_tags', ['entity_type_id' => 'entity_test_with_bundle', 'bundle' => 'bundle_a']);
|
||||||
|
$bundle_b_url = Url::fromRoute('cache_test_list.bundle_tags', ['entity_type_id' => 'entity_test_with_bundle', 'bundle' => 'bundle_b']);
|
||||||
|
$this->drupalGet($bundle_a_url);
|
||||||
|
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'MISS');
|
||||||
|
$this->assertCacheTags(['rendered', 'entity_test_with_bundle_list:bundle_a']);
|
||||||
|
|
||||||
|
$this->drupalGet($bundle_a_url);
|
||||||
|
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT');
|
||||||
|
$this->assertCacheTags(['rendered', 'entity_test_with_bundle_list:bundle_a']);
|
||||||
|
$this->drupalGet($bundle_b_url);
|
||||||
|
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'MISS');
|
||||||
|
$this->assertCacheTags(['rendered', 'entity_test_with_bundle_list:bundle_b']);
|
||||||
|
$this->drupalGet($bundle_b_url);
|
||||||
|
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT');
|
||||||
|
$entity1 = EntityTestWithBundle::create(['type' => 'bundle_a', 'name' => 'entity1']);
|
||||||
|
$entity1->save();
|
||||||
|
// Check that tags are invalidated after creating an entity of the current
|
||||||
|
// bundle.
|
||||||
|
$this->drupalGet($bundle_a_url);
|
||||||
|
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'MISS');
|
||||||
|
$this->drupalGet($bundle_a_url);
|
||||||
|
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT');
|
||||||
|
// Check that tags are not invalidated after creating an entity of a
|
||||||
|
// different bundle than the current in the request.
|
||||||
|
$this->drupalGet($bundle_b_url);
|
||||||
|
$this->assertSession()->responseHeaderEquals('X-Drupal-Cache', 'HIT');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -443,6 +443,43 @@ class EntityUnitTest extends UnitTestCase {
|
||||||
$this->entity->postSave($storage, TRUE);
|
$this->entity->postSave($storage, TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::postSave
|
||||||
|
*/
|
||||||
|
public function testPostSaveBundle() {
|
||||||
|
$this->cacheTagsInvalidator->expects($this->at(0))
|
||||||
|
->method('invalidateTags')
|
||||||
|
->with([
|
||||||
|
// List cache tag.
|
||||||
|
$this->entityTypeId . '_list',
|
||||||
|
$this->entityTypeId . '_list:' . $this->entity->bundle(),
|
||||||
|
]);
|
||||||
|
$this->cacheTagsInvalidator->expects($this->at(1))
|
||||||
|
->method('invalidateTags')
|
||||||
|
->with([
|
||||||
|
// Own cache tag.
|
||||||
|
$this->entityTypeId . ':' . $this->values['id'],
|
||||||
|
// List cache tag.
|
||||||
|
$this->entityTypeId . '_list',
|
||||||
|
$this->entityTypeId . '_list:' . $this->entity->bundle(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->entityType->expects($this->atLeastOnce())
|
||||||
|
->method('hasKey')
|
||||||
|
->with('bundle')
|
||||||
|
->willReturn(TRUE);
|
||||||
|
|
||||||
|
// This method is internal, so check for errors on calling it only.
|
||||||
|
$storage = $this->createMock('\Drupal\Core\Entity\EntityStorageInterface');
|
||||||
|
|
||||||
|
// A creation should trigger the invalidation of the global list cache tag
|
||||||
|
// and the one for the bundle.
|
||||||
|
$this->entity->postSave($storage, FALSE);
|
||||||
|
// An update should trigger the invalidation of the "list", bundle list and
|
||||||
|
// the "own" cache tags.
|
||||||
|
$this->entity->postSave($storage, TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers ::preCreate
|
* @covers ::preCreate
|
||||||
*/
|
*/
|
||||||
|
@ -493,6 +530,30 @@ class EntityUnitTest extends UnitTestCase {
|
||||||
$this->entity->postDelete($storage, $entities);
|
$this->entity->postDelete($storage, $entities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @covers ::postDelete
|
||||||
|
*/
|
||||||
|
public function testPostDeleteBundle() {
|
||||||
|
$this->cacheTagsInvalidator->expects($this->once())
|
||||||
|
->method('invalidateTags')
|
||||||
|
->with([
|
||||||
|
$this->entityTypeId . ':' . $this->values['id'],
|
||||||
|
$this->entityTypeId . '_list',
|
||||||
|
$this->entityTypeId . '_list:' . $this->entity->bundle(),
|
||||||
|
]);
|
||||||
|
$this->entityType->expects($this->atLeastOnce())
|
||||||
|
->method('hasKey')
|
||||||
|
->with('bundle')
|
||||||
|
->willReturn(TRUE);
|
||||||
|
$storage = $this->createMock('\Drupal\Core\Entity\EntityStorageInterface');
|
||||||
|
$storage->expects($this->once())
|
||||||
|
->method('getEntityType')
|
||||||
|
->willReturn($this->entityType);
|
||||||
|
|
||||||
|
$entities = [$this->values['id'] => $this->entity];
|
||||||
|
$this->entity->postDelete($storage, $entities);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @covers ::postLoad
|
* @covers ::postLoad
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue