View source
<?php
declare (strict_types=1);
namespace Drupal\Tests\system\Functional\Entity;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableDependencyInterface;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Url;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\field\Entity\FieldConfig;
use Drupal\Tests\system\Functional\Cache\PageCacheTagsTestBase;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
protected static $modules = [
'entity_test',
'field_test',
];
protected $entity;
protected $referencingEntity;
protected $nonReferencingEntity;
protected function setUp() : void {
parent::setUp();
$user_role = Role::load(RoleInterface::ANONYMOUS_ID);
$user_role
->grantPermission('view test entity');
$user_role
->save();
$this->entity = $this
->createEntity();
if ($this->entity
->getEntityType()
->get('field_ui_base_route')) {
FieldStorageConfig::create([
'field_name' => 'configurable_field',
'entity_type' => $this->entity
->getEntityTypeId(),
'type' => 'test_field',
'settings' => [],
])
->save();
FieldConfig::create([
'entity_type' => $this->entity
->getEntityTypeId(),
'bundle' => $this->entity
->bundle(),
'field_name' => 'configurable_field',
'label' => 'Configurable field',
'settings' => [],
])
->save();
$storage = $this->container
->get('entity_type.manager')
->getStorage($this->entity
->getEntityTypeId());
$storage
->resetCache();
$this->entity = $storage
->load($this->entity
->id());
}
[
$this->referencingEntity,
$this->nonReferencingEntity,
] = $this
->createReferenceTestEntities($this->entity);
}
protected abstract function createEntity();
protected function getAccessCacheContextsForEntity(EntityInterface $entity) {
return [];
}
protected function getAdditionalCacheContextsForEntity(EntityInterface $entity) {
return [];
}
protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
return [];
}
protected function getAdditionalCacheContextsForEntityListing() {
return [];
}
protected function getAdditionalCacheTagsForEntityListing() {
return [];
}
protected function selectViewMode($entity_type) {
$view_modes = \Drupal::entityTypeManager()
->getStorage('entity_view_mode')
->loadByProperties([
'targetEntityType' => $entity_type,
]);
if (empty($view_modes)) {
return 'default';
}
else {
if (isset($view_modes[$entity_type . '.full'])) {
return 'full';
}
else {
$view_modes = array_keys($view_modes);
return substr($view_modes[0], strlen($entity_type) + 1);
}
}
}
protected function createReferenceTestEntities($referenced_entity) {
$entity_type = 'entity_test';
$bundle = 'foo';
entity_test_create_bundle($bundle, NULL, $entity_type);
$field_name = $referenced_entity
->getEntityTypeId() . '_reference';
FieldStorageConfig::create([
'field_name' => $field_name,
'entity_type' => $entity_type,
'type' => 'entity_reference',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
'settings' => [
'target_type' => $referenced_entity
->getEntityTypeId(),
],
])
->save();
FieldConfig::create([
'field_name' => $field_name,
'entity_type' => $entity_type,
'bundle' => $bundle,
'settings' => [
'handler' => 'default',
'handler_settings' => [
'target_bundles' => [
$referenced_entity
->bundle() => $referenced_entity
->bundle(),
],
'sort' => [
'field' => '_none',
],
'auto_create' => FALSE,
],
],
])
->save();
$display_repository = \Drupal::service('entity_display.repository');
if (!$this->entity
->getEntityType()
->hasHandlerClass('view_builder')) {
$display_repository
->getViewDisplay($entity_type, $bundle, 'full')
->setComponent($field_name, [
'type' => 'entity_reference_label',
])
->save();
}
else {
$referenced_entity_view_mode = $this
->selectViewMode($this->entity
->getEntityTypeId());
$display_repository
->getViewDisplay($entity_type, $bundle, 'full')
->setComponent($field_name, [
'type' => 'entity_reference_entity_view',
'settings' => [
'view_mode' => $referenced_entity_view_mode,
],
])
->save();
}
$label_key = \Drupal::entityTypeManager()
->getDefinition($entity_type)
->getKey('label');
$referencing_entity = $this->container
->get('entity_type.manager')
->getStorage($entity_type)
->create([
$label_key => 'Referencing ' . $entity_type,
'status' => 1,
'type' => $bundle,
$field_name => [
'target_id' => $referenced_entity
->id(),
],
]);
$referencing_entity
->save();
$non_referencing_entity = $this->container
->get('entity_type.manager')
->getStorage($entity_type)
->create([
$label_key => 'Non-referencing ' . $entity_type,
'status' => 1,
'type' => $bundle,
]);
$non_referencing_entity
->save();
return [
$referencing_entity,
$non_referencing_entity,
];
}
public function testReferencedEntity() {
$entity_type = $this->entity
->getEntityTypeId();
$referencing_entity_url = $this->referencingEntity
->toUrl('canonical');
$non_referencing_entity_url = $this->nonReferencingEntity
->toUrl('canonical');
$listing_url = Url::fromRoute('entity.entity_test.collection_referencing_entities', [
'entity_reference_field_name' => $entity_type . '_reference',
'referenced_entity_type' => $entity_type,
'referenced_entity_id' => $this->entity
->id(),
]);
$empty_entity_listing_url = Url::fromRoute('entity.entity_test.collection_empty', [
'entity_type_id' => $entity_type,
]);
$nonempty_entity_listing_url = Url::fromRoute('entity.entity_test.collection_labels_alphabetically', [
'entity_type_id' => $entity_type,
]);
$default_cache_contexts = [
'languages:' . LanguageInterface::TYPE_INTERFACE,
'theme',
'user.permissions',
];
$entity_cache_contexts = Cache::mergeContexts($default_cache_contexts, [
'url.site',
]);
$page_cache_contexts = Cache::mergeContexts($default_cache_contexts, [
'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT,
'user.roles:authenticated',
]);
$page_cache_tags = Cache::mergeTags([
'http_response',
'rendered',
], [
'config:user.role.anonymous',
]);
$page_cache_tags = Cache::mergeTags($page_cache_tags, \Drupal::moduleHandler()
->moduleExists('block') ? [
'config:block_list',
] : []);
$page_cache_tags_referencing_entity = in_array('user.permissions', $this
->getAccessCacheContextsForEntity($this->referencingEntity)) ? [
'config:user.role.anonymous',
] : [];
$view_cache_tag = [];
if ($this->entity
->getEntityType()
->hasHandlerClass('view_builder')) {
$view_cache_tag = \Drupal::entityTypeManager()
->getViewBuilder($entity_type)
->getCacheTags();
}
$context_metadata = \Drupal::service('cache_contexts_manager')
->convertTokensToKeys($entity_cache_contexts);
$cache_context_tags = $context_metadata
->getCacheTags();
$referencing_entity_cache_tags = Cache::mergeTags($this->referencingEntity
->getCacheTags(), \Drupal::entityTypeManager()
->getViewBuilder('entity_test')
->getCacheTags());
$referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $this->entity
->getCacheTags());
$referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $this
->getAdditionalCacheTagsForEntity($this->entity));
$referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $view_cache_tag);
$referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $cache_context_tags);
$referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, [
'rendered',
]);
$non_referencing_entity_cache_tags = Cache::mergeTags($this->nonReferencingEntity
->getCacheTags(), \Drupal::entityTypeManager()
->getViewBuilder('entity_test')
->getCacheTags());
$non_referencing_entity_cache_tags = Cache::mergeTags($non_referencing_entity_cache_tags, [
'rendered',
]);
$empty_entity_listing_cache_tags = Cache::mergeTags($this->entity
->getEntityType()
->getListCacheTags(), $page_cache_tags);
$nonempty_entity_listing_cache_tags = Cache::mergeTags($this->entity
->getEntityType()
->getListCacheTags(), $this->entity
->getCacheTags());
$nonempty_entity_listing_cache_tags = Cache::mergeTags($nonempty_entity_listing_cache_tags, $this
->getAdditionalCacheTagsForEntityListing());
$nonempty_entity_listing_cache_tags = Cache::mergeTags($nonempty_entity_listing_cache_tags, $page_cache_tags);
$this
->verifyPageCache($referencing_entity_url, 'MISS');
$expected_tags = Cache::mergeTags($referencing_entity_cache_tags, $page_cache_tags);
$expected_tags = Cache::mergeTags($expected_tags, $page_cache_tags_referencing_entity);
$this
->verifyPageCache($referencing_entity_url, 'HIT', $expected_tags);
$cache_keys = [
'entity_view',
'entity_test',
$this->referencingEntity
->id(),
'full',
];
$access_cache_contexts = $this
->getAccessCacheContextsForEntity($this->entity);
$additional_cache_contexts = $this
->getAdditionalCacheContextsForEntity($this->referencingEntity);
if (count($access_cache_contexts) || count($additional_cache_contexts)) {
$cache_contexts = Cache::mergeContexts($entity_cache_contexts, $additional_cache_contexts);
$cache_contexts = Cache::mergeContexts($cache_contexts, $access_cache_contexts);
$context_metadata = \Drupal::service('cache_contexts_manager')
->convertTokensToKeys($cache_contexts);
$referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $context_metadata
->getCacheTags());
}
$this
->verifyRenderCache($cache_keys, $referencing_entity_cache_tags, (new CacheableMetadata())
->setCacheContexts($entity_cache_contexts));
$this
->verifyPageCache($non_referencing_entity_url, 'MISS');
$this
->verifyPageCache($non_referencing_entity_url, 'HIT', Cache::mergeTags($non_referencing_entity_cache_tags, $page_cache_tags));
$cache_keys = [
'entity_view',
'entity_test',
$this->nonReferencingEntity
->id(),
'full',
];
$this
->verifyRenderCache($cache_keys, $non_referencing_entity_cache_tags, (new CacheableMetadata())
->setCacheContexts($entity_cache_contexts));
$this
->verifyPageCache($listing_url, 'MISS');
$expected_tags = Cache::mergeTags($referencing_entity_cache_tags, $page_cache_tags);
$expected_tags = Cache::mergeTags($expected_tags, $page_cache_tags_referencing_entity);
$this
->verifyPageCache($listing_url, 'HIT', $expected_tags);
$this
->verifyPageCache($empty_entity_listing_url, 'MISS');
$this
->verifyPageCache($empty_entity_listing_url, 'HIT', $empty_entity_listing_cache_tags);
$contexts_in_header = $this
->getSession()
->getResponseHeader('X-Drupal-Cache-Contexts');
$this
->assertEqualsCanonicalizing(Cache::mergeContexts($page_cache_contexts, $this
->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
$this
->verifyPageCache($nonempty_entity_listing_url, 'MISS', $nonempty_entity_listing_cache_tags);
$this
->verifyPageCache($nonempty_entity_listing_url, 'HIT', $nonempty_entity_listing_cache_tags);
$contexts_in_header = $this
->getSession()
->getResponseHeader('X-Drupal-Cache-Contexts');
$this
->assertEqualsCanonicalizing(Cache::mergeContexts($page_cache_contexts, $this
->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
$this->entity
->save();
$this
->verifyPageCache($referencing_entity_url, 'MISS');
$this
->verifyPageCache($listing_url, 'MISS');
$this
->verifyPageCache($empty_entity_listing_url, 'MISS');
$this
->verifyPageCache($nonempty_entity_listing_url, 'MISS');
$this
->verifyPageCache($non_referencing_entity_url, 'HIT');
$this
->verifyPageCache($referencing_entity_url, 'HIT');
$this
->verifyPageCache($listing_url, 'HIT');
$this
->verifyPageCache($empty_entity_listing_url, 'HIT');
$this
->verifyPageCache($nonempty_entity_listing_url, 'HIT');
$this->referencingEntity
->save();
$this
->verifyPageCache($referencing_entity_url, 'MISS');
$this
->verifyPageCache($listing_url, 'MISS');
$this
->verifyPageCache($nonempty_entity_listing_url, 'HIT');
$this
->verifyPageCache($non_referencing_entity_url, 'HIT');
$this
->verifyPageCache($empty_entity_listing_url, 'HIT');
$this
->verifyPageCache($referencing_entity_url, 'HIT');
$this
->verifyPageCache($listing_url, 'HIT');
$this
->verifyPageCache($nonempty_entity_listing_url, 'HIT');
$this->nonReferencingEntity
->save();
$this
->verifyPageCache($referencing_entity_url, 'HIT');
$this
->verifyPageCache($listing_url, 'HIT');
$this
->verifyPageCache($empty_entity_listing_url, 'HIT');
$this
->verifyPageCache($nonempty_entity_listing_url, 'HIT');
$this
->verifyPageCache($non_referencing_entity_url, 'MISS');
$this
->verifyPageCache($non_referencing_entity_url, 'HIT');
if ($this->entity
->getEntityType()
->hasHandlerClass('view_builder')) {
$referenced_entity_view_mode = $this
->selectViewMode($this->entity
->getEntityTypeId());
$display_repository = \Drupal::service('entity_display.repository');
$entity_display = $display_repository
->getViewDisplay($entity_type, $this->entity
->bundle(), $referenced_entity_view_mode);
$entity_display
->save();
$this
->verifyPageCache($referencing_entity_url, 'MISS');
$this
->verifyPageCache($listing_url, 'MISS');
$this
->verifyPageCache($non_referencing_entity_url, 'HIT');
$this
->verifyPageCache($empty_entity_listing_url, 'HIT');
$this
->verifyPageCache($nonempty_entity_listing_url, 'HIT');
$this
->verifyPageCache($referencing_entity_url, 'HIT');
$this
->verifyPageCache($listing_url, 'HIT');
}
if ($bundle_entity_type_id = $this->entity
->getEntityType()
->getBundleEntityType()) {
$bundle_entity = $this->container
->get('entity_type.manager')
->getStorage($bundle_entity_type_id)
->load($this->entity
->bundle());
$bundle_entity
->save();
$this
->verifyPageCache($referencing_entity_url, 'MISS');
$this
->verifyPageCache($listing_url, 'MISS');
$this
->verifyPageCache($non_referencing_entity_url, 'HIT');
$is_special_case = $bundle_entity
->getCacheTags() == $this->entity
->getCacheTags() && $bundle_entity
->getEntityType()
->getListCacheTags() == $this->entity
->getEntityType()
->getListCacheTags();
if ($is_special_case) {
$this
->verifyPageCache($empty_entity_listing_url, 'MISS');
$this
->verifyPageCache($nonempty_entity_listing_url, 'MISS');
}
else {
$this
->verifyPageCache($empty_entity_listing_url, 'HIT');
$this
->verifyPageCache($nonempty_entity_listing_url, 'HIT');
}
$this
->verifyPageCache($referencing_entity_url, 'HIT');
$this
->verifyPageCache($listing_url, 'HIT');
if ($is_special_case) {
$this
->verifyPageCache($empty_entity_listing_url, 'HIT');
$this
->verifyPageCache($nonempty_entity_listing_url, 'HIT');
}
}
if ($this->entity
->getEntityType()
->get('field_ui_base_route')) {
$field_storage_name = $this->entity
->getEntityTypeId() . '.configurable_field';
$field_storage = FieldStorageConfig::load($field_storage_name);
$field_storage
->save();
$this
->verifyPageCache($referencing_entity_url, 'MISS');
$this
->verifyPageCache($listing_url, 'MISS');
$this
->verifyPageCache($empty_entity_listing_url, 'HIT');
$this
->verifyPageCache($nonempty_entity_listing_url, 'HIT');
$this
->verifyPageCache($non_referencing_entity_url, 'HIT');
$this
->verifyPageCache($referencing_entity_url, 'HIT');
$this
->verifyPageCache($listing_url, 'HIT');
$field_name = $this->entity
->getEntityTypeId() . '.' . $this->entity
->bundle() . '.configurable_field';
$field = FieldConfig::load($field_name);
$field
->save();
$this
->verifyPageCache($referencing_entity_url, 'MISS');
$this
->verifyPageCache($listing_url, 'MISS');
$this
->verifyPageCache($empty_entity_listing_url, 'HIT');
$this
->verifyPageCache($nonempty_entity_listing_url, 'HIT');
$this
->verifyPageCache($non_referencing_entity_url, 'HIT');
$this
->verifyPageCache($referencing_entity_url, 'HIT');
$this
->verifyPageCache($listing_url, 'HIT');
}
Cache::invalidateTags($this->entity
->getCacheTagsToInvalidate());
$this
->verifyPageCache($referencing_entity_url, 'MISS');
$this
->verifyPageCache($listing_url, 'MISS');
$this
->verifyPageCache($nonempty_entity_listing_url, 'MISS');
$this
->verifyPageCache($non_referencing_entity_url, 'HIT');
$this
->verifyPageCache($empty_entity_listing_url, 'HIT');
$this
->verifyPageCache($referencing_entity_url, 'HIT');
$this
->verifyPageCache($listing_url, 'HIT');
$this
->verifyPageCache($nonempty_entity_listing_url, 'HIT');
Cache::invalidateTags($this->entity
->getEntityType()
->getListCacheTags());
$this
->verifyPageCache($empty_entity_listing_url, 'MISS');
$this
->verifyPageCache($nonempty_entity_listing_url, 'MISS');
$this
->verifyPageCache($referencing_entity_url, 'HIT');
$this
->verifyPageCache($non_referencing_entity_url, 'HIT');
$this
->verifyPageCache($listing_url, 'HIT');
$this
->verifyPageCache($empty_entity_listing_url, 'HIT');
$this
->verifyPageCache($nonempty_entity_listing_url, 'HIT');
if (!empty($view_cache_tag)) {
Cache::invalidateTags($view_cache_tag);
$this
->verifyPageCache($referencing_entity_url, 'MISS');
$this
->verifyPageCache($listing_url, 'MISS');
$this
->verifyPageCache($non_referencing_entity_url, 'HIT');
$this
->verifyPageCache($empty_entity_listing_url, 'HIT');
$this
->verifyPageCache($nonempty_entity_listing_url, 'HIT');
$this
->verifyPageCache($referencing_entity_url, 'HIT');
$this
->verifyPageCache($listing_url, 'HIT');
}
$this->entity
->delete();
$this
->verifyPageCache($referencing_entity_url, 'MISS');
$this
->verifyPageCache($listing_url, 'MISS');
$this
->verifyPageCache($empty_entity_listing_url, 'MISS');
$this
->verifyPageCache($nonempty_entity_listing_url, 'MISS');
$this
->verifyPageCache($non_referencing_entity_url, 'HIT');
$referencing_entity_cache_tags = Cache::mergeTags($this->referencingEntity
->getCacheTags(), \Drupal::entityTypeManager()
->getViewBuilder('entity_test')
->getCacheTags());
$referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, [
'http_response',
'rendered',
]);
$nonempty_entity_listing_cache_tags = Cache::mergeTags($this->entity
->getEntityType()
->getListCacheTags(), $this
->getAdditionalCacheTagsForEntityListing());
$nonempty_entity_listing_cache_tags = Cache::mergeTags($nonempty_entity_listing_cache_tags, $page_cache_tags);
$this
->verifyPageCache($referencing_entity_url, 'HIT', Cache::mergeTags($referencing_entity_cache_tags, $page_cache_tags));
$this
->verifyPageCache($listing_url, 'HIT', $page_cache_tags);
$this
->verifyPageCache($empty_entity_listing_url, 'HIT', $empty_entity_listing_cache_tags);
$this
->verifyPageCache($nonempty_entity_listing_url, 'HIT', $nonempty_entity_listing_cache_tags);
}
protected function createCacheId(array $keys, array $contexts) {
@trigger_error(__FUNCTION__ . '() is deprecated in drupal:10.2.0 and is removed from drupal:11.0.0. There is no replacement. See https://www.drupal.org/node/3354596', E_USER_DEPRECATED);
$cid_parts = $keys;
$contexts = \Drupal::service('cache_contexts_manager')
->convertTokensToKeys($contexts);
$cid_parts = array_merge($cid_parts, $contexts
->getKeys());
return implode(':', $cid_parts);
}
protected function verifyRenderCache(array $keys, array $tags, CacheableDependencyInterface $cacheability) {
$cache_bin = $this
->getRenderCacheBackend();
$cache_entry = $cache_bin
->get($keys, $cacheability);
$this
->assertInstanceOf(\stdClass::class, $cache_entry);
sort($cache_entry->tags);
sort($tags);
$this
->assertSame($cache_entry->tags, $tags);
}
protected function getRenderCacheBackend() {
$variation_cache_factory = \Drupal::service('variation_cache_factory');
return $variation_cache_factory
->get('render');
}
}