3 namespace Drupal\system\Tests\Entity;
5 use Drupal\Core\Cache\Cache;
6 use Drupal\Core\Entity\EntityInterface;
7 use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
8 use Drupal\Core\Field\FieldStorageDefinitionInterface;
9 use Drupal\Core\Language\LanguageInterface;
11 use Drupal\field\Entity\FieldStorageConfig;
12 use Drupal\field\Entity\FieldConfig;
13 use Drupal\system\Tests\Cache\PageCacheTagsTestBase;
14 use Drupal\user\Entity\Role;
15 use Drupal\user\RoleInterface;
17 @trigger_error(__NAMESPACE__ . '\EntityCacheTagsTestBase is deprecated in Drupal 8.6.x and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\system\Functional\Entity\EntityCacheTagsTestBase. See https://www.drupal.org/node/2946549.', E_USER_DEPRECATED);
20 * Provides helper methods for Entity cache tags tests.
22 * @deprecated Scheduled for removal in Drupal 9.0.0.
23 * Use \Drupal\Tests\system\Functional\Entity\EntityCacheTagsTestBase instead.
25 * @see https://www.drupal.org/node/2946549
27 abstract class EntityCacheTagsTestBase extends PageCacheTagsTestBase {
34 public static $modules = ['entity_test', 'field_test'];
37 * The main entity used for testing.
39 * @var \Drupal\Core\Entity\EntityInterface
44 * The entity instance referencing the main entity.
46 * @var \Drupal\Core\Entity\EntityInterface
48 protected $referencingEntity;
51 * The entity instance not referencing the main entity.
53 * @var \Drupal\Core\Entity\EntityInterface
55 protected $nonReferencingEntity;
60 protected function setUp() {
63 // Give anonymous users permission to view test entities, so that we can
64 // verify the cache tags of cached versions of test entity pages.
65 $user_role = Role::load(RoleInterface::ANONYMOUS_ID);
66 $user_role->grantPermission('view test entity');
70 $this->entity = $this->createEntity();
72 // If this is an entity with field UI enabled, then add a configurable
73 // field. We will use this configurable field in later tests to ensure that
74 // field configuration invalidate render cache entries.
75 if ($this->entity->getEntityType()->get('field_ui_base_route')) {
76 // Add field, so we can modify the field storage and field entities to
77 // verify that changes to those indeed clear cache tags.
78 FieldStorageConfig::create([
79 'field_name' => 'configurable_field',
80 'entity_type' => $this->entity->getEntityTypeId(),
81 'type' => 'test_field',
85 'entity_type' => $this->entity->getEntityTypeId(),
86 'bundle' => $this->entity->bundle(),
87 'field_name' => 'configurable_field',
88 'label' => 'Configurable field',
92 // Reload the entity now that a new field has been added to it.
93 $storage = $this->container
94 ->get('entity.manager')
95 ->getStorage($this->entity->getEntityTypeId());
96 $storage->resetCache();
97 $this->entity = $storage->load($this->entity->id());
100 // Create a referencing and a non-referencing entity.
102 $this->referencingEntity,
103 $this->nonReferencingEntity,
104 ) = $this->createReferenceTestEntities($this->entity);
108 * Generates standardized entity cache tags test info.
110 * @param string $entity_type_label
111 * The label of the entity type whose cache tags to test.
112 * @param string $group
117 * @see \Drupal\simpletest\TestBase::getInfo()
119 protected static function generateStandardizedInfo($entity_type_label, $group) {
121 'name' => "$entity_type_label entity cache tags",
122 'description' => "Test the $entity_type_label entity's cache tags.",
128 * Creates the entity to be tested.
130 * @return \Drupal\Core\Entity\EntityInterface
131 * The entity to be tested.
133 abstract protected function createEntity();
136 * Returns the access cache contexts for the tested entity.
138 * Only list cache contexts that aren't part of the required cache contexts.
140 * @param \Drupal\Core\Entity\EntityInterface $entity
141 * The entity to be tested, as created by createEntity().
144 * An array of the additional cache contexts.
146 * @see \Drupal\Core\Entity\EntityAccessControlHandlerInterface
148 protected function getAccessCacheContextsForEntity(EntityInterface $entity) {
153 * Returns the additional (non-standard) cache contexts for the tested entity.
155 * Only list cache contexts that aren't part of the required cache contexts.
157 * @param \Drupal\Core\Entity\EntityInterface $entity
158 * The entity to be tested, as created by createEntity().
161 * An array of the additional cache contexts.
163 * @see \Drupal\system\Tests\Entity\EntityCacheTagsTestBase::createEntity()
165 protected function getAdditionalCacheContextsForEntity(EntityInterface $entity) {
170 * Returns the additional (non-standard) cache tags for the tested entity.
172 * @param \Drupal\Core\Entity\EntityInterface $entity
173 * The entity to be tested, as created by createEntity().
175 * An array of the additional cache tags.
177 * @see \Drupal\system\Tests\Entity\EntityCacheTagsTestBase::createEntity()
179 protected function getAdditionalCacheTagsForEntity(EntityInterface $entity) {
184 * Returns the additional cache tags for the tested entity's listing by type.
187 * An array of the additional cache contexts.
189 protected function getAdditionalCacheContextsForEntityListing() {
194 * Returns the additional cache tags for the tested entity's listing by type.
196 * Necessary when there are unavoidable default entities of this type, e.g.
197 * the anonymous and administrator User entities always exist.
200 * An array of the additional cache tags.
202 protected function getAdditionalCacheTagsForEntityListing() {
207 * Selects the preferred view mode for the given entity type.
209 * Prefers 'full', picks the first one otherwise, and if none are available,
212 protected function selectViewMode($entity_type) {
213 $view_modes = \Drupal::entityManager()
214 ->getStorage('entity_view_mode')
215 ->loadByProperties(['targetEntityType' => $entity_type]);
217 if (empty($view_modes)) {
221 // Prefer the "full" display mode.
222 if (isset($view_modes[$entity_type . '.full'])) {
226 $view_modes = array_keys($view_modes);
227 return substr($view_modes[0], strlen($entity_type) + 1);
233 * Creates a referencing and a non-referencing entity for testing purposes.
235 * @param \Drupal\Core\Entity\EntityInterface $referenced_entity
236 * The entity that the referencing entity should reference.
238 * @return \Drupal\Core\Entity\EntityInterface[]
239 * An array containing a referencing entity and a non-referencing entity.
241 protected function createReferenceTestEntities($referenced_entity) {
242 // All referencing entities should be of the type 'entity_test'.
243 $entity_type = 'entity_test';
245 // Create a "foo" bundle for the given entity type.
247 entity_test_create_bundle($bundle, NULL, $entity_type);
249 // Add a field of the given type to the given entity type's "foo" bundle.
250 $field_name = $referenced_entity->getEntityTypeId() . '_reference';
251 FieldStorageConfig::create([
252 'field_name' => $field_name,
253 'entity_type' => $entity_type,
254 'type' => 'entity_reference',
255 'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
257 'target_type' => $referenced_entity->getEntityTypeId(),
260 FieldConfig::create([
261 'field_name' => $field_name,
262 'entity_type' => $entity_type,
265 'handler' => 'default',
266 'handler_settings' => [
267 'target_bundles' => [
268 $referenced_entity->bundle() => $referenced_entity->bundle(),
270 'sort' => ['field' => '_none'],
271 'auto_create' => FALSE,
275 if (!$this->entity->getEntityType()->hasHandlerClass('view_builder')) {
276 entity_get_display($entity_type, $bundle, 'full')
277 ->setComponent($field_name, [
278 'type' => 'entity_reference_label',
283 $referenced_entity_view_mode = $this->selectViewMode($this->entity->getEntityTypeId());
284 entity_get_display($entity_type, $bundle, 'full')
285 ->setComponent($field_name, [
286 'type' => 'entity_reference_entity_view',
288 'view_mode' => $referenced_entity_view_mode,
294 // Create an entity that does reference the entity being tested.
295 $label_key = \Drupal::entityManager()->getDefinition($entity_type)->getKey('label');
296 $referencing_entity = $this->container->get('entity_type.manager')
297 ->getStorage($entity_type)
299 $label_key => 'Referencing ' . $entity_type,
302 $field_name => ['target_id' => $referenced_entity->id()],
304 $referencing_entity->save();
306 // Create an entity that does not reference the entity being tested.
307 $non_referencing_entity = $this->container->get('entity_type.manager')
308 ->getStorage($entity_type)
310 $label_key => 'Non-referencing ' . $entity_type,
314 $non_referencing_entity->save();
318 $non_referencing_entity,
323 * Tests cache tags presence and invalidation of the entity when referenced.
325 * Tests the following cache tags:
326 * - entity type view cache tag: "<entity type>_view"
327 * - entity cache tag: "<entity type>:<entity ID>"
328 * - entity type list cache tag: "<entity type>_list"
329 * - referencing entity type view cache tag: "<referencing entity type>_view"
330 * - referencing entity type cache tag: "<referencing entity type>:<referencing entity ID>"
332 public function testReferencedEntity() {
333 $entity_type = $this->entity->getEntityTypeId();
334 $referencing_entity_url = $this->referencingEntity->urlInfo('canonical');
335 $non_referencing_entity_url = $this->nonReferencingEntity->urlInfo('canonical');
336 $listing_url = Url::fromRoute('entity.entity_test.collection_referencing_entities', [
337 'entity_reference_field_name' => $entity_type . '_reference',
338 'referenced_entity_type' => $entity_type,
339 'referenced_entity_id' => $this->entity->id(),
341 $empty_entity_listing_url = Url::fromRoute('entity.entity_test.collection_empty', ['entity_type_id' => $entity_type]);
342 $nonempty_entity_listing_url = Url::fromRoute('entity.entity_test.collection_labels_alphabetically', ['entity_type_id' => $entity_type]);
344 // The default cache contexts for rendered entities.
345 $default_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'];
346 $entity_cache_contexts = $default_cache_contexts;
347 $page_cache_contexts = Cache::mergeContexts($default_cache_contexts, ['url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT]);
349 // Cache tags present on every rendered page.
350 // 'user.permissions' is a required cache context, and responses that vary
351 // by this cache context when requested by anonymous users automatically
352 // also get this cache tag, to ensure correct invalidation.
353 $page_cache_tags = Cache::mergeTags(['http_response', 'rendered'], ['config:user.role.anonymous']);
354 // If the block module is used, the Block page display variant is used,
355 // which adds the block config entity type's list cache tags.
356 $page_cache_tags = Cache::mergeTags($page_cache_tags, \Drupal::moduleHandler()->moduleExists('block') ? ['config:block_list'] : []);
358 $page_cache_tags_referencing_entity = in_array('user.permissions', $this->getAccessCacheContextsForEntity($this->referencingEntity)) ? ['config:user.role.anonymous'] : [];
360 $view_cache_tag = [];
361 if ($this->entity->getEntityType()->hasHandlerClass('view_builder')) {
362 $view_cache_tag = \Drupal::entityManager()->getViewBuilder($entity_type)
366 $context_metadata = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($entity_cache_contexts);
367 $cache_context_tags = $context_metadata->getCacheTags();
369 // Generate the cache tags for the (non) referencing entities.
370 $referencing_entity_cache_tags = Cache::mergeTags($this->referencingEntity->getCacheTags(), \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags());
371 // Includes the main entity's cache tags, since this entity references it.
372 $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $this->entity->getCacheTags());
373 $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $this->getAdditionalCacheTagsForEntity($this->entity));
374 $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $view_cache_tag);
375 $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $cache_context_tags);
376 $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, ['rendered']);
378 $non_referencing_entity_cache_tags = Cache::mergeTags($this->nonReferencingEntity->getCacheTags(), \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags());
379 $non_referencing_entity_cache_tags = Cache::mergeTags($non_referencing_entity_cache_tags, ['rendered']);
381 // Generate the cache tags for all two possible entity listing paths.
382 // 1. list cache tag only (listing query has no match)
383 // 2. list cache tag plus entity cache tag (listing query has a match)
384 $empty_entity_listing_cache_tags = Cache::mergeTags($this->entity->getEntityType()->getListCacheTags(), $page_cache_tags);
386 $nonempty_entity_listing_cache_tags = Cache::mergeTags($this->entity->getEntityType()->getListCacheTags(), $this->entity->getCacheTags());
387 $nonempty_entity_listing_cache_tags = Cache::mergeTags($nonempty_entity_listing_cache_tags, $this->getAdditionalCacheTagsForEntityListing($this->entity));
388 $nonempty_entity_listing_cache_tags = Cache::mergeTags($nonempty_entity_listing_cache_tags, $page_cache_tags);
390 $this->pass("Test referencing entity.", 'Debug');
391 $this->verifyPageCache($referencing_entity_url, 'MISS');
393 // Verify a cache hit, but also the presence of the correct cache tags.
394 $expected_tags = Cache::mergeTags($referencing_entity_cache_tags, $page_cache_tags);
395 $expected_tags = Cache::mergeTags($expected_tags, $page_cache_tags_referencing_entity);
396 $this->verifyPageCache($referencing_entity_url, 'HIT', $expected_tags);
398 // Also verify the existence of an entity render cache entry.
399 $cache_keys = ['entity_view', 'entity_test', $this->referencingEntity->id(), 'full'];
400 $cid = $this->createCacheId($cache_keys, $entity_cache_contexts);
401 $access_cache_contexts = $this->getAccessCacheContextsForEntity($this->entity);
402 $additional_cache_contexts = $this->getAdditionalCacheContextsForEntity($this->referencingEntity);
403 $redirected_cid = NULL;
404 if (count($access_cache_contexts) || count($additional_cache_contexts)) {
405 $cache_contexts = Cache::mergeContexts($entity_cache_contexts, $additional_cache_contexts);
406 $cache_contexts = Cache::mergeContexts($cache_contexts, $access_cache_contexts);
407 $redirected_cid = $this->createCacheId($cache_keys, $cache_contexts);
408 $context_metadata = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($cache_contexts);
409 $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, $context_metadata->getCacheTags());
411 $this->verifyRenderCache($cid, $referencing_entity_cache_tags, $redirected_cid);
413 $this->pass("Test non-referencing entity.", 'Debug');
414 $this->verifyPageCache($non_referencing_entity_url, 'MISS');
415 // Verify a cache hit, but also the presence of the correct cache tags.
416 $this->verifyPageCache($non_referencing_entity_url, 'HIT', Cache::mergeTags($non_referencing_entity_cache_tags, $page_cache_tags));
417 // Also verify the existence of an entity render cache entry.
418 $cache_keys = ['entity_view', 'entity_test', $this->nonReferencingEntity->id(), 'full'];
419 $cid = $this->createCacheId($cache_keys, $entity_cache_contexts);
420 $this->verifyRenderCache($cid, $non_referencing_entity_cache_tags);
422 $this->pass("Test listing of referencing entities.", 'Debug');
423 // Prime the page cache for the listing of referencing entities.
424 $this->verifyPageCache($listing_url, 'MISS');
426 // Verify a cache hit, but also the presence of the correct cache tags.
427 $expected_tags = Cache::mergeTags($referencing_entity_cache_tags, $page_cache_tags);
428 $expected_tags = Cache::mergeTags($expected_tags, $page_cache_tags_referencing_entity);
429 $this->verifyPageCache($listing_url, 'HIT', $expected_tags);
431 $this->pass("Test empty listing.", 'Debug');
432 // Prime the page cache for the empty listing.
433 $this->verifyPageCache($empty_entity_listing_url, 'MISS');
434 // Verify a cache hit, but also the presence of the correct cache tags.
435 $this->verifyPageCache($empty_entity_listing_url, 'HIT', $empty_entity_listing_cache_tags);
436 // Verify the entity type's list cache contexts are present.
437 $contexts_in_header = $this->drupalGetHeader('X-Drupal-Cache-Contexts');
438 $this->assertEqual(Cache::mergeContexts($page_cache_contexts, $this->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
440 $this->pass("Test listing containing referenced entity.", 'Debug');
441 // Prime the page cache for the listing containing the referenced entity.
442 $this->verifyPageCache($nonempty_entity_listing_url, 'MISS', $nonempty_entity_listing_cache_tags);
443 // Verify a cache hit, but also the presence of the correct cache tags.
444 $this->verifyPageCache($nonempty_entity_listing_url, 'HIT', $nonempty_entity_listing_cache_tags);
445 // Verify the entity type's list cache contexts are present.
446 $contexts_in_header = $this->drupalGetHeader('X-Drupal-Cache-Contexts');
447 $this->assertEqual(Cache::mergeContexts($page_cache_contexts, $this->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
449 // Verify that after modifying the referenced entity, there is a cache miss
450 // for every route except the one for the non-referencing entity.
451 $this->pass("Test modification of referenced entity.", 'Debug');
452 $this->entity->save();
453 $this->verifyPageCache($referencing_entity_url, 'MISS');
454 $this->verifyPageCache($listing_url, 'MISS');
455 $this->verifyPageCache($empty_entity_listing_url, 'MISS');
456 $this->verifyPageCache($nonempty_entity_listing_url, 'MISS');
457 $this->verifyPageCache($non_referencing_entity_url, 'HIT');
459 // Verify cache hits.
460 $this->verifyPageCache($referencing_entity_url, 'HIT');
461 $this->verifyPageCache($listing_url, 'HIT');
462 $this->verifyPageCache($empty_entity_listing_url, 'HIT');
463 $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
465 // Verify that after modifying the referencing entity, there is a cache miss
466 // for every route except the ones for the non-referencing entity and the
467 // empty entity listing.
468 $this->pass("Test modification of referencing entity.", 'Debug');
469 $this->referencingEntity->save();
470 $this->verifyPageCache($referencing_entity_url, 'MISS');
471 $this->verifyPageCache($listing_url, 'MISS');
472 $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
473 $this->verifyPageCache($non_referencing_entity_url, 'HIT');
474 $this->verifyPageCache($empty_entity_listing_url, 'HIT');
476 // Verify cache hits.
477 $this->verifyPageCache($referencing_entity_url, 'HIT');
478 $this->verifyPageCache($listing_url, 'HIT');
479 $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
481 // Verify that after modifying the non-referencing entity, there is a cache
482 // miss only for the non-referencing entity route.
483 $this->pass("Test modification of non-referencing entity.", 'Debug');
484 $this->nonReferencingEntity->save();
485 $this->verifyPageCache($referencing_entity_url, 'HIT');
486 $this->verifyPageCache($listing_url, 'HIT');
487 $this->verifyPageCache($empty_entity_listing_url, 'HIT');
488 $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
489 $this->verifyPageCache($non_referencing_entity_url, 'MISS');
491 // Verify cache hits.
492 $this->verifyPageCache($non_referencing_entity_url, 'HIT');
494 if ($this->entity->getEntityType()->hasHandlerClass('view_builder')) {
495 // Verify that after modifying the entity's display, there is a cache miss
496 // for both the referencing entity, and the listing of referencing
497 // entities, but not for any other routes.
498 $referenced_entity_view_mode = $this->selectViewMode($this->entity->getEntityTypeId());
499 $this->pass("Test modification of referenced entity's '$referenced_entity_view_mode' display.", 'Debug');
500 $entity_display = entity_get_display($entity_type, $this->entity->bundle(), $referenced_entity_view_mode);
501 $entity_display->save();
502 $this->verifyPageCache($referencing_entity_url, 'MISS');
503 $this->verifyPageCache($listing_url, 'MISS');
504 $this->verifyPageCache($non_referencing_entity_url, 'HIT');
505 $this->verifyPageCache($empty_entity_listing_url, 'HIT');
506 $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
508 // Verify cache hits.
509 $this->verifyPageCache($referencing_entity_url, 'HIT');
510 $this->verifyPageCache($listing_url, 'HIT');
513 if ($bundle_entity_type_id = $this->entity->getEntityType()->getBundleEntityType()) {
514 // Verify that after modifying the corresponding bundle entity, there is a
515 // cache miss for both the referencing entity, and the listing of
516 // referencing entities, but not for any other routes.
517 $this->pass("Test modification of referenced entity's bundle entity.", 'Debug');
518 $bundle_entity = $this->container->get('entity_type.manager')
519 ->getStorage($bundle_entity_type_id)
520 ->load($this->entity->bundle());
521 $bundle_entity->save();
522 $this->verifyPageCache($referencing_entity_url, 'MISS');
523 $this->verifyPageCache($listing_url, 'MISS');
524 $this->verifyPageCache($non_referencing_entity_url, 'HIT');
525 // Special case: entity types may choose to use their bundle entity type
526 // cache tags, to avoid having excessively granular invalidation.
527 $is_special_case = $bundle_entity->getCacheTags() == $this->entity->getCacheTags() && $bundle_entity->getEntityType()->getListCacheTags() == $this->entity->getEntityType()->getListCacheTags();
528 if ($is_special_case) {
529 $this->verifyPageCache($empty_entity_listing_url, 'MISS');
530 $this->verifyPageCache($nonempty_entity_listing_url, 'MISS');
533 $this->verifyPageCache($empty_entity_listing_url, 'HIT');
534 $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
537 // Verify cache hits.
538 $this->verifyPageCache($referencing_entity_url, 'HIT');
539 $this->verifyPageCache($listing_url, 'HIT');
540 if ($is_special_case) {
541 $this->verifyPageCache($empty_entity_listing_url, 'HIT');
542 $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
546 if ($this->entity->getEntityType()->get('field_ui_base_route')) {
547 // Verify that after modifying a configurable field on the entity, there
549 $this->pass("Test modification of referenced entity's configurable field.", 'Debug');
550 $field_storage_name = $this->entity->getEntityTypeId() . '.configurable_field';
551 $field_storage = FieldStorageConfig::load($field_storage_name);
552 $field_storage->save();
553 $this->verifyPageCache($referencing_entity_url, 'MISS');
554 $this->verifyPageCache($listing_url, 'MISS');
555 $this->verifyPageCache($empty_entity_listing_url, 'HIT');
556 $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
557 $this->verifyPageCache($non_referencing_entity_url, 'HIT');
559 // Verify cache hits.
560 $this->verifyPageCache($referencing_entity_url, 'HIT');
561 $this->verifyPageCache($listing_url, 'HIT');
563 // Verify that after modifying a configurable field on the entity, there
565 $this->pass("Test modification of referenced entity's configurable field.", 'Debug');
566 $field_name = $this->entity->getEntityTypeId() . '.' . $this->entity->bundle() . '.configurable_field';
567 $field = FieldConfig::load($field_name);
569 $this->verifyPageCache($referencing_entity_url, 'MISS');
570 $this->verifyPageCache($listing_url, 'MISS');
571 $this->verifyPageCache($empty_entity_listing_url, 'HIT');
572 $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
573 $this->verifyPageCache($non_referencing_entity_url, 'HIT');
575 // Verify cache hits.
576 $this->verifyPageCache($referencing_entity_url, 'HIT');
577 $this->verifyPageCache($listing_url, 'HIT');
580 // Verify that after invalidating the entity's cache tag directly, there is
581 // a cache miss for every route except the ones for the non-referencing
582 // entity and the empty entity listing.
583 $this->pass("Test invalidation of referenced entity's cache tag.", 'Debug');
584 Cache::invalidateTags($this->entity->getCacheTagsToInvalidate());
585 $this->verifyPageCache($referencing_entity_url, 'MISS');
586 $this->verifyPageCache($listing_url, 'MISS');
587 $this->verifyPageCache($nonempty_entity_listing_url, 'MISS');
588 $this->verifyPageCache($non_referencing_entity_url, 'HIT');
589 $this->verifyPageCache($empty_entity_listing_url, 'HIT');
591 // Verify cache hits.
592 $this->verifyPageCache($referencing_entity_url, 'HIT');
593 $this->verifyPageCache($listing_url, 'HIT');
594 $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
596 // Verify that after invalidating the entity's list cache tag directly,
597 // there is a cache miss for both the empty entity listing and the non-empty
598 // entity listing routes, but not for other routes.
599 $this->pass("Test invalidation of referenced entity's list cache tag.", 'Debug');
600 Cache::invalidateTags($this->entity->getEntityType()->getListCacheTags());
601 $this->verifyPageCache($empty_entity_listing_url, 'MISS');
602 $this->verifyPageCache($nonempty_entity_listing_url, 'MISS');
603 $this->verifyPageCache($referencing_entity_url, 'HIT');
604 $this->verifyPageCache($non_referencing_entity_url, 'HIT');
605 $this->verifyPageCache($listing_url, 'HIT');
607 // Verify cache hits.
608 $this->verifyPageCache($empty_entity_listing_url, 'HIT');
609 $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
611 if (!empty($view_cache_tag)) {
612 // Verify that after invalidating the generic entity type's view cache tag
613 // directly, there is a cache miss for both the referencing entity, and the
614 // listing of referencing entities, but not for other routes.
615 $this->pass("Test invalidation of referenced entity's 'view' cache tag.", 'Debug');
616 Cache::invalidateTags($view_cache_tag);
617 $this->verifyPageCache($referencing_entity_url, 'MISS');
618 $this->verifyPageCache($listing_url, 'MISS');
619 $this->verifyPageCache($non_referencing_entity_url, 'HIT');
620 $this->verifyPageCache($empty_entity_listing_url, 'HIT');
621 $this->verifyPageCache($nonempty_entity_listing_url, 'HIT');
623 // Verify cache hits.
624 $this->verifyPageCache($referencing_entity_url, 'HIT');
625 $this->verifyPageCache($listing_url, 'HIT');
628 // Verify that after deleting the entity, there is a cache miss for every
629 // route except for the non-referencing entity one.
630 $this->pass('Test deletion of referenced entity.', 'Debug');
631 $this->entity->delete();
632 $this->verifyPageCache($referencing_entity_url, 'MISS');
633 $this->verifyPageCache($listing_url, 'MISS');
634 $this->verifyPageCache($empty_entity_listing_url, 'MISS');
635 $this->verifyPageCache($nonempty_entity_listing_url, 'MISS');
636 $this->verifyPageCache($non_referencing_entity_url, 'HIT');
638 // Verify cache hits.
639 $referencing_entity_cache_tags = Cache::mergeTags($this->referencingEntity->getCacheTags(), \Drupal::entityManager()->getViewBuilder('entity_test')->getCacheTags());
640 $referencing_entity_cache_tags = Cache::mergeTags($referencing_entity_cache_tags, ['http_response', 'rendered']);
642 $nonempty_entity_listing_cache_tags = Cache::mergeTags($this->entity->getEntityType()->getListCacheTags(), $this->getAdditionalCacheTagsForEntityListing());
643 $nonempty_entity_listing_cache_tags = Cache::mergeTags($nonempty_entity_listing_cache_tags, $page_cache_tags);
645 $this->verifyPageCache($referencing_entity_url, 'HIT', Cache::mergeTags($referencing_entity_cache_tags, $page_cache_tags));
646 $this->verifyPageCache($listing_url, 'HIT', $page_cache_tags);
647 $this->verifyPageCache($empty_entity_listing_url, 'HIT', $empty_entity_listing_cache_tags);
648 $this->verifyPageCache($nonempty_entity_listing_url, 'HIT', $nonempty_entity_listing_cache_tags);
652 * Creates a cache ID from a list of cache keys and a set of cache contexts.
654 * @param string[] $keys
655 * A list of cache keys.
656 * @param string[] $contexts
657 * A set of cache contexts.
660 * The cache ID string.
662 protected function createCacheId(array $keys, array $contexts) {
665 $contexts = \Drupal::service('cache_contexts_manager')->convertTokensToKeys($contexts);
666 $cid_parts = array_merge($cid_parts, $contexts->getKeys());
668 return implode(':', $cid_parts);
672 * Verify that a given render cache entry exists, with the correct cache tags.
675 * The render cache item ID.
677 * An array of expected cache tags.
678 * @param string|null $redirected_cid
679 * (optional) The redirected render cache item ID.
681 protected function verifyRenderCache($cid, array $tags, $redirected_cid = NULL) {
682 // Also verify the existence of an entity render cache entry.
683 $cache_entry = \Drupal::cache('render')->get($cid);
684 $this->assertTrue($cache_entry, 'A render cache entry exists.');
685 sort($cache_entry->tags);
687 $this->assertIdentical($cache_entry->tags, $tags);
688 $is_redirecting_cache_item = isset($cache_entry->data['#cache_redirect']);
689 if ($redirected_cid === NULL) {
690 $this->assertFalse($is_redirecting_cache_item, 'Render cache entry is not a redirect.');
691 // If this is a redirecting cache item unlike we expected, log it.
692 if ($is_redirecting_cache_item) {
693 debug($cache_entry->data);
697 // Verify that $cid contains a cache redirect.
698 $this->assertTrue($is_redirecting_cache_item, 'Render cache entry is a redirect.');
699 // If this is not a redirecting cache item unlike we expected, log it.
700 if (!$is_redirecting_cache_item) {
701 debug($cache_entry->data);
703 // Verify that the cache redirect points to the expected CID.
704 $redirect_cache_metadata = $cache_entry->data['#cache'];
705 $actual_redirection_cid = $this->createCacheId(
706 $redirect_cache_metadata['keys'],
707 $redirect_cache_metadata['contexts']
709 $this->assertIdentical($redirected_cid, $actual_redirection_cid);
710 // Finally, verify that the redirected CID exists and has the same cache
712 $this->verifyRenderCache($redirected_cid, $tags);