From e8fa2b37daaa9710a6f12737171f097ea9fc1f6f Mon Sep 17 00:00:00 2001
From: catch <6915-catch@users.noreply.drupalcode.org>
Date: Sun, 16 Mar 2025 09:16:07 +0000
Subject: [PATCH] Issue #3501508 by prudloff, smustgrave: Add a way to
 programmatically generate bundle list cache tags

---
 core/lib/Drupal/Core/Entity/EntityBase.php            |  5 ++++-
 core/lib/Drupal/Core/Entity/EntityType.php            |  7 +++++++
 core/lib/Drupal/Core/Entity/EntityTypeInterface.php   | 11 +++++++++++
 .../tests/Drupal/Tests/Core/Entity/EntityTypeTest.php | 11 +++++++++++
 .../tests/Drupal/Tests/Core/Entity/EntityUnitTest.php |  4 ++++
 5 files changed, 37 insertions(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Entity/EntityBase.php b/core/lib/Drupal/Core/Entity/EntityBase.php
index aa25d63c0067..6b25963c5162 100644
--- a/core/lib/Drupal/Core/Entity/EntityBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityBase.php
@@ -483,7 +483,10 @@ public function getCacheContexts() {
   protected function getListCacheTagsToInvalidate() {
     $tags = $this->getEntityType()->getListCacheTags();
     if ($this->getEntityType()->hasKey('bundle')) {
-      $tags[] = $this->getEntityTypeId() . '_list:' . $this->bundle();
+      $tags = Cache::mergeTags(
+        $tags,
+        $this->getEntityType()->getBundleListCacheTags($this->bundle())
+      );
     }
     return $tags;
   }
diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php
index d9e8ffd806aa..9eab50b9a222 100644
--- a/core/lib/Drupal/Core/Entity/EntityType.php
+++ b/core/lib/Drupal/Core/Entity/EntityType.php
@@ -865,6 +865,13 @@ public function getListCacheTags() {
     return $this->list_cache_tags;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getBundleListCacheTags(string $bundle): array {
+    return [$this->id() . '_list:' . $bundle];
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
index c7275d31fef8..4522902f76bb 100644
--- a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
@@ -733,6 +733,17 @@ public function getListCacheContexts();
    */
   public function getListCacheTags();
 
+  /**
+   * The list cache tags associated with a specific bundle.
+   *
+   * Enables code listing entities of this type and bundle to ensure that newly created
+   * entities show up immediately.
+   *
+   * @return string[]
+   *   An array of the cache tags for this bundle.
+   */
+  public function getBundleListCacheTags(string $bundle): array;
+
   /**
    * Gets the key that is used to store configuration dependencies.
    *
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php
index d810500b1a0e..5db0f96c1e8b 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityTypeTest.php
@@ -487,4 +487,15 @@ public function testEntityClassImplements(): void {
     $this->assertFalse($entity_type->entityClassImplements(\DateTimeInterface::class));
   }
 
+  /**
+   * Tests the ::getBundleListCacheTags() method.
+   *
+   * @covers ::getBundleListCacheTags
+   */
+  public function testGetBundleListCacheTags(): void {
+    $entity_type = $this->setUpEntityType(['entity_keys' => ['id' => 'id']]);
+    $bundle = $this->randomMachineName();
+    $this->assertEquals([$entity_type->id() . '_list:' . $bundle], $entity_type->getBundleListCacheTags($bundle));
+  }
+
 }
diff --git a/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php b/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php
index c1c1f58894e0..3357139aaa53 100644
--- a/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/EntityUnitTest.php
@@ -102,6 +102,10 @@ protected function setUp(): void {
     $this->entityType->expects($this->any())
       ->method('getListCacheTags')
       ->willReturn([$this->entityTypeId . '_list']);
+    $this->entityType->expects($this->any())
+      ->method('getBundleListCacheTags')
+      ->with($this->entityTypeId)
+      ->willReturn([$this->entityTypeId . '_list:' . $this->entityTypeId]);
 
     $this->entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);
     $this->entityTypeManager->expects($this->any())
-- 
GitLab