diff --git a/src/Entity/MetaEntity.php b/src/Entity/MetaEntity.php index 0ae4fa45ec9222f2afe7bfd52e7d5fbf578158cc..fa3b22cc53d71d6e2600b91c1431fdd13dd144f4 100644 --- a/src/Entity/MetaEntity.php +++ b/src/Entity/MetaEntity.php @@ -185,11 +185,34 @@ class MetaEntity extends ContentEntityBase implements MetaEntityInterface { * {@inheritdoc} */ public function getCacheTagsToInvalidate(): array { - $tags = parent::getCacheTagsToInvalidate(); - if ($target_entity = $this->getTargetEntity()) { - $tags = Cache::mergeTags($tags, $target_entity->getCacheTags()); + return Cache::mergeTags( + parent::getCacheTagsToInvalidate(), + $this->getTargetEntityCacheTagsToInvalidate(), + $this->cacheTags + ); + } + + /** + * {@inheritdoc} + */ + public function invalidateTagsOnSave($update): void { + parent::invalidateTagsOnSave($update); + // We should invalidate target entity cache; regardless if the meta entity + // is new, or it was just updated. + Cache::invalidateTags($this->getTargetEntityCacheTagsToInvalidate()); + } + + /** + * Returns the cache tags of the target entity to invalidate. + * + * @return string[] + * The cache tags of the target entity to invalidate. + */ + protected function getTargetEntityCacheTagsToInvalidate(): array { + if (!$target_entity = $this->getTargetEntity()) { + return []; } - return Cache::mergeTags($tags, $this->cacheTags); + return $target_entity->getCacheTagsToInvalidate(); } } diff --git a/tests/src/Functional/MetaEntityCacheTagsTest.php b/tests/src/Functional/MetaEntityCacheTagsTest.php index 4d8249d17eca5d12a8e915663adce993ce61e11f..d285b14869dc7cdd33595d183481fd3edc455d67 100644 --- a/tests/src/Functional/MetaEntityCacheTagsTest.php +++ b/tests/src/Functional/MetaEntityCacheTagsTest.php @@ -5,6 +5,8 @@ declare(strict_types = 1); namespace Drupal\Tests\meta_entity\Functional; use Drupal\Core\Cache\Cache; +use Drupal\Core\Entity\ContentEntityInterface; +use Drupal\Core\Entity\ContentEntityStorageInterface; use Drupal\Core\Entity\EntityInterface; use Drupal\entity_test\Entity\EntityTest; use Drupal\meta_entity\Entity\MetaEntity; @@ -112,6 +114,45 @@ class MetaEntityCacheTagsTest extends EntityWithUriCacheTagsTestBase { $this->verifyPageCache($this->arbitraryEntity->toUrl(), 'HIT'); } + /** + * Tests target entity cache tag invalidation with a new meta entity. + */ + public function testTargetEntityCacheClearOnNew(): void { + $new_entity = $this->entityValidateAndSave(EntityTest::create([ + 'id' => 10, + 'name' => 'Test', + ])); + + // Cache the target entity render array. + $this->verifyPageCache($new_entity->toUrl(), 'MISS'); + $this->verifyPageCache($new_entity->toUrl(), 'HIT'); + + $meta = MetaEntity::loadOrCreate('visit_count', $new_entity); + $this->assertTrue($meta->isNew()); + $meta = $this->entityValidateAndSave($meta->set('count', 0)); + + $this->verifyPageCache($new_entity->toUrl(), 'MISS'); + $this->verifyPageCache($new_entity->toUrl(), 'HIT'); + + $this->assertSame( + [ + 'meta_entity:2', + 'entity_test:10', + ], + $meta->getCacheTagsToInvalidate() + ); + $meta_list_tags_ref = (new \ReflectionClass($meta)) + ->getMethod('getListCacheTagsToInvalidate'); + $meta_list_tags_ref->setAccessible(TRUE); + $this->assertSame( + [ + 'meta_entity_list', + 'meta_entity_list:visit_count', + ], + $meta_list_tags_ref->invoke($meta) + ); + } + /** * {@inheritdoc} */ @@ -133,4 +174,25 @@ class MetaEntityCacheTagsTest extends EntityWithUriCacheTagsTestBase { ); } + /** + * Validates, saves and reloads an entity from its storage. + * + * @param \Drupal\Core\Entity\ContentEntityInterface $entity + * The entity to validate, save and reload. + * + * @return \Drupal\Core\Entity\ContentEntityInterface + * The entity, freshly loaded from the storage. + */ + protected function entityValidateAndSave(ContentEntityInterface $entity): ContentEntityInterface { + $this->assertEmpty($entity->validate()); + $entity->save(); + $storage = $this->container->get('entity_type.manager') + ->getStorage($entity->getEntityTypeId()); + $this->assertInstanceOf(ContentEntityStorageInterface::class, $storage); + $storage->resetCache([$entity->id()]); + $entity = $storage->load($entity->id()); + $this->assertInstanceOf(ContentEntityInterface::class, $entity); + return $entity; + } + }