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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
public function getCacheTagsToInvalidate() {
|
||||
// @todo Add bundle-specific listing cache tag?
|
||||
// https://www.drupal.org/node/2145751
|
||||
if ($this->isNew()) {
|
||||
return [];
|
||||
}
|
||||
|
@ -563,7 +575,7 @@ abstract class EntityBase implements EntityInterface {
|
|||
// 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->getEntityType()->getListCacheTags();
|
||||
$tags = $this->getListCacheTagsToInvalidate();
|
||||
if ($this->hasLinkTemplate('canonical')) {
|
||||
// Creating or updating an entity may change a cached 403 or 404 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
|
||||
// must invalidate its list cache tags as well.)
|
||||
$tags = Cache::mergeTags($tags, $entity->getCacheTagsToInvalidate());
|
||||
$tags = Cache::mergeTags($tags, $entity->getListCacheTagsToInvalidate());
|
||||
}
|
||||
Cache::invalidateTags($tags);
|
||||
}
|
||||
|
|
|
@ -4,3 +4,10 @@ cache_test.url_bubbling:
|
|||
_controller: '\Drupal\cache_test\Controller\CacheTestController::urlBubbling'
|
||||
requirements:
|
||||
_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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
@ -493,6 +530,30 @@ class EntityUnitTest extends UnitTestCase {
|
|||
$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
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue