Same name and namespace in other branches
  1. 8.9.x core/modules/system/tests/src/Functional/Entity/EntityCacheTagsTestBase.php \Drupal\Tests\system\Functional\Entity\EntityCacheTagsTestBase::testReferencedEntity()
  2. 9 core/modules/system/tests/src/Functional/Entity/EntityCacheTagsTestBase.php \Drupal\Tests\system\Functional\Entity\EntityCacheTagsTestBase::testReferencedEntity()

Tests cache tags presence and invalidation of the entity when referenced.

Tests the following cache tags:

  • entity type view cache tag: "<entity type>_view"
  • entity cache tag: "<entity type>:<entity ID>"
  • entity type list cache tag: "<entity type>_list"
  • referencing entity type view cache tag: "<referencing entity type>_view"
  • referencing entity type cache tag: "<referencing entity type>:<referencing entity ID>"

File

core/modules/system/tests/src/Functional/Entity/EntityCacheTagsTestBase.php, line 312

Class

EntityCacheTagsTestBase
Provides helper methods for Entity cache tags tests.

Namespace

Drupal\Tests\system\Functional\Entity

Code

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,
  ]);

  // The default cache contexts for rendered entities.
  $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',
  ]);

  // Cache tags present on every rendered page.
  // 'user.permissions' is a required cache context, and responses that vary
  // by this cache context when requested by anonymous users automatically
  // also get this cache tag, to ensure correct invalidation.
  $page_cache_tags = Cache::mergeTags([
    'http_response',
    'rendered',
  ], [
    'config:user.role.anonymous',
  ]);

  // If the block module is used, the Block page display variant is used,
  // which adds the block config entity type's list cache tags.
  $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();

  // Generate the cache tags for the (non) referencing entities.
  $referencing_entity_cache_tags = Cache::mergeTags($this->referencingEntity
    ->getCacheTags(), \Drupal::entityTypeManager()
    ->getViewBuilder('entity_test')
    ->getCacheTags());

  // Includes the main entity's cache tags, since this entity references it.
  $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',
  ]);

  // 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
    ->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');

  // Verify a cache hit, but also the presence of the correct cache tags.
  $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);

  // Also verify the existence of an entity render cache entry.
  $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');

  // Verify a cache hit, but also the presence of the correct cache tags.
  $this
    ->verifyPageCache($non_referencing_entity_url, 'HIT', Cache::mergeTags($non_referencing_entity_cache_tags, $page_cache_tags));

  // Also verify the existence of an entity render cache entry.
  $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));

  // Prime the page cache for the listing of referencing entities.
  $this
    ->verifyPageCache($listing_url, 'MISS');

  // Verify a cache hit, but also the presence of the correct cache tags.
  $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);

  // Prime the page cache for the empty listing.
  $this
    ->verifyPageCache($empty_entity_listing_url, 'MISS');

  // Verify a cache hit, but also the presence of the correct cache tags.
  $this
    ->verifyPageCache($empty_entity_listing_url, 'HIT', $empty_entity_listing_cache_tags);

  // Verify the entity type's list cache contexts are present.
  $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));

  // Prime the page cache for the listing containing the referenced entity.
  $this
    ->verifyPageCache($nonempty_entity_listing_url, 'MISS', $nonempty_entity_listing_cache_tags);

  // Verify a cache hit, but also the presence of the correct cache tags.
  $this
    ->verifyPageCache($nonempty_entity_listing_url, 'HIT', $nonempty_entity_listing_cache_tags);

  // Verify the entity type's list cache contexts are present.
  $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));

  // Verify that after modifying the referenced entity, there is a cache miss
  // for every route except the one for the non-referencing entity.
  $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');

  // Verify cache hits.
  $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');

  // Verify that after modifying the referencing entity, there is a cache miss
  // for every route except the ones for the non-referencing entity and the
  // empty entity listing.
  $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');

  // Verify cache hits.
  $this
    ->verifyPageCache($referencing_entity_url, 'HIT');
  $this
    ->verifyPageCache($listing_url, 'HIT');
  $this
    ->verifyPageCache($nonempty_entity_listing_url, 'HIT');

  // Verify that after modifying the non-referencing entity, there is a cache
  // miss only for the non-referencing entity route.
  $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');

  // Verify cache hits.
  $this
    ->verifyPageCache($non_referencing_entity_url, 'HIT');
  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 any other routes.
    $referenced_entity_view_mode = $this
      ->selectViewMode($this->entity
      ->getEntityTypeId());

    /** @var \Drupal\Core\Entity\EntityDisplayRepositoryInterface $display_repository */
    $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');

    // Verify cache hits.
    $this
      ->verifyPageCache($referencing_entity_url, 'HIT');
    $this
      ->verifyPageCache($listing_url, 'HIT');
  }
  if ($bundle_entity_type_id = $this->entity
    ->getEntityType()
    ->getBundleEntityType()) {

    // 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 any other routes.
    $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');

    // 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
      ->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');
    }

    // Verify cache hits.
    $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')) {

    // Verify that after modifying a configurable field on the entity, there
    // is a cache miss.
    $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');

    // Verify cache hits.
    $this
      ->verifyPageCache($referencing_entity_url, 'HIT');
    $this
      ->verifyPageCache($listing_url, 'HIT');

    // Verify that after modifying a configurable field on the entity, there
    // is a cache miss.
    $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');

    // Verify cache hits.
    $this
      ->verifyPageCache($referencing_entity_url, 'HIT');
    $this
      ->verifyPageCache($listing_url, 'HIT');
  }

  // 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.
  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');

  // Verify cache hits.
  $this
    ->verifyPageCache($referencing_entity_url, 'HIT');
  $this
    ->verifyPageCache($listing_url, 'HIT');
  $this
    ->verifyPageCache($nonempty_entity_listing_url, '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.
  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');

  // Verify cache hits.
  $this
    ->verifyPageCache($empty_entity_listing_url, 'HIT');
  $this
    ->verifyPageCache($nonempty_entity_listing_url, '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 other routes.
    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');

    // Verify cache hits.
    $this
      ->verifyPageCache($referencing_entity_url, 'HIT');
    $this
      ->verifyPageCache($listing_url, 'HIT');
  }

  // Verify that after deleting the entity, there is a cache miss for every
  // route except for the non-referencing entity one.
  $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');

  // Verify cache hits.
  $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);
}