diff --git a/cache_tools.info.yml b/cache_tools.info.yml
index 1aaf90f..bfd7f87 100644
--- a/cache_tools.info.yml
+++ b/cache_tools.info.yml
@@ -2,4 +2,4 @@ type: module
 name: Cache Tools
 description: Module contains caching related tools.
 package: Cache
-core_version_requirement: ^8.8 || ^9 || ^10 || ^11
+core_version_requirement: ^10.1 || ^11 || ^12
diff --git a/cache_tools.module b/cache_tools.module
index c05ef57..d2f2156 100644
--- a/cache_tools.module
+++ b/cache_tools.module
@@ -5,15 +5,16 @@
  * Provides various caching enhancements.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\cache_tools\Hook\CacheToolsHooks;
 use Drupal\Core\Entity\EntityInterface;
 
 /**
  * Implements hook_entity_type_alter().
  */
+#[LegacyHook]
 function cache_tools_entity_type_alter(array &$entity_types) {
-  /** @var \Drupal\Core\Config\Entity\ConfigEntityType $block */
-  $block = $entity_types['block'];
-  $block->setViewBuilderClass('Drupal\cache_tools\CachewiseBlockViewBuilder');
+  \Drupal::service(CacheToolsHooks::class)->entityTypeAlter($entity_types);
 }
 
 /**
@@ -27,11 +28,9 @@ function cache_tools_entity_type_alter(array &$entity_types) {
  *   Use a PublishedEntityCacheTag event subscriber instead of hooks after:
  *   https://www.drupal.org/project/drupal/issues/2551893 .
  */
+#[LegacyHook]
 function cache_tools_entity_insert(EntityInterface $entity) {
-  /** @var \Drupal\cache_tools\Service\CacheInvalidator $cache_invalidator */
-  $cache_invalidator = \Drupal::service('cache_tools.cache.invalidator');
-  $cache_invalidator->invalidatePublishedEntity($entity);
-  $cache_invalidator->invalidatePublishedEntityFields($entity);
+  \Drupal::service(CacheToolsHooks::class)->entityInsert($entity);
 }
 
 /**
@@ -42,11 +41,9 @@ function cache_tools_entity_insert(EntityInterface $entity) {
  * a published state and/or a configured field value is changed. Entity type or
  * a field needs to be allowed for invalidation.
  */
+#[LegacyHook]
 function cache_tools_entity_update(EntityInterface $entity) {
-  /** @var \Drupal\cache_tools\Service\CacheInvalidator $cache_invalidator */
-  $cache_invalidator = \Drupal::service('cache_tools.cache.invalidator');
-  $cache_invalidator->invalidatePublishedEntity($entity);
-  $cache_invalidator->invalidatePublishedEntityFields($entity);
+  \Drupal::service(CacheToolsHooks::class)->entityUpdate($entity);
 }
 
 /**
@@ -57,8 +54,7 @@ function cache_tools_entity_update(EntityInterface $entity) {
  * Entity type or a field needs to be allowed for invalidation.
  * Note: Deleted entities are invalidated via entity_type:id (eg node:123).
  */
+#[LegacyHook]
 function cache_tools_entity_delete(EntityInterface $entity) {
-  /** @var \Drupal\cache_tools\Service\CacheInvalidator $cache_invalidator */
-  $cache_invalidator = \Drupal::service('cache_tools.cache.invalidator');
-  $cache_invalidator->invalidatePublishedEntityFields($entity);
+  \Drupal::service(CacheToolsHooks::class)->entityDelete($entity);
 }
diff --git a/cache_tools.services.yml b/cache_tools.services.yml
index 97bf38b..fb18843 100644
--- a/cache_tools.services.yml
+++ b/cache_tools.services.yml
@@ -42,3 +42,7 @@ parameters:
           contexts:
             exclude:
              - url.query_args
+
+  Drupal\cache_tools\Hook\CacheToolsHooks:
+    class: Drupal\cache_tools\Hook\CacheToolsHooks
+    autowire: true
diff --git a/src/Hook/CacheToolsHooks.php b/src/Hook/CacheToolsHooks.php
new file mode 100644
index 0000000..7edc2bc
--- /dev/null
+++ b/src/Hook/CacheToolsHooks.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace Drupal\cache_tools\Hook;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for cache_tools.
+ */
+class CacheToolsHooks {
+
+  /**
+   * Implements hook_entity_type_alter().
+   */
+  #[Hook('entity_type_alter')]
+  public function entityTypeAlter(array &$entity_types) {
+    /** @var \Drupal\Core\Config\Entity\ConfigEntityType $block */
+    $block = $entity_types['block'];
+    $block->setViewBuilderClass('Drupal\cache_tools\CachewiseBlockViewBuilder');
+  }
+
+  /**
+   * Implements hook_entity_insert().
+   *
+   * Invalidates `entitytype_entitybundle_pub` and
+   * `entitytype_entitybundle_pub:field_name:value` if entity is going to be
+   * published. Entity type or a field needs to be allowed for invalidation.
+   *
+   * @todo
+   *   Use a PublishedEntityCacheTag event subscriber instead of hooks after:
+   *   https://www.drupal.org/project/drupal/issues/2551893 .
+   */
+  #[Hook('entity_insert')]
+  public function entityInsert(EntityInterface $entity) {
+    /** @var \Drupal\cache_tools\Service\CacheInvalidator $cache_invalidator */
+    $cache_invalidator = \Drupal::service('cache_tools.cache.invalidator');
+    $cache_invalidator->invalidatePublishedEntity($entity);
+    $cache_invalidator->invalidatePublishedEntityFields($entity);
+  }
+
+  /**
+   * Implements hook_entity_update().
+   *
+   * Invalidates `entitytype_entitybundle_pub` if entity is going from unpublished
+   * state to published and `entitytype_entitybundle_pub:field_name:value` if
+   * a published state and/or a configured field value is changed. Entity type or
+   * a field needs to be allowed for invalidation.
+   */
+  #[Hook('entity_update')]
+  public function entityUpdate(EntityInterface $entity) {
+    /** @var \Drupal\cache_tools\Service\CacheInvalidator $cache_invalidator */
+    $cache_invalidator = \Drupal::service('cache_tools.cache.invalidator');
+    $cache_invalidator->invalidatePublishedEntity($entity);
+    $cache_invalidator->invalidatePublishedEntityFields($entity);
+  }
+
+  /**
+   * Implements hook_entity_delete().
+   *
+   * Invalidates `entitytype_entitybundle_pub:field_name:value` if deleting
+   * published entity.
+   * Entity type or a field needs to be allowed for invalidation.
+   * Note: Deleted entities are invalidated via entity_type:id (eg node:123).
+   */
+  #[Hook('entity_delete')]
+  public function entityDelete(EntityInterface $entity) {
+    /** @var \Drupal\cache_tools\Service\CacheInvalidator $cache_invalidator */
+    $cache_invalidator = \Drupal::service('cache_tools.cache.invalidator');
+    $cache_invalidator->invalidatePublishedEntityFields($entity);
+  }
+
+}
diff --git a/src/Service/CacheInvalidator.php b/src/Service/CacheInvalidator.php
index aee4a2f..028e296 100644
--- a/src/Service/CacheInvalidator.php
+++ b/src/Service/CacheInvalidator.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\cache_tools\Service;
 
+use Drupal\Component\Utility\DeprecationHelper;
 use drunomics\ServiceUtils\Core\Entity\EntityTypeManagerTrait;
 use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
 use Drupal\Core\Entity\EntityPublishedInterface;
@@ -103,8 +104,8 @@ class CacheInvalidator {
     ];
     // If this is update we need to take a look at original entity as well.
     $entities = [$entity];
-    if ($entity->original) {
-      $entities[] = $entity->original;
+    if (DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => $entity->getOriginal(), fn() => $entity->original)) {
+      $entities[] = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => $entity->getOriginal(), fn() => $entity->original);
     }
     // If entity is going to be unpublished (insert)
     // or stays unpublished (update), skip.
@@ -207,8 +208,8 @@ class CacheInvalidator {
    * @param string $tag_prefix_field
    *   Prefix for generated tags.
    * @param bool $invalidate_term_parents
-   *  Flag to indicate invalidation strategy.
-   *  Only applicable for entities of type taxonomy_term.
+   *   Flag to indicate invalidation strategy.
+   *   Only applicable for entities of type taxonomy_term.
    *
    * @return array
    *   An array of cache tags to invalidate.
@@ -255,8 +256,8 @@ class CacheInvalidator {
     // Determine published status and assume the entity is published if it
     // doesn't implement the interface.
     $is_published = $entity instanceof EntityPublishedInterface ? $entity->isPublished() : TRUE;
-    if (isset($entity->original)) {
-      $was_published = $entity->original instanceof EntityPublishedInterface ? $entity->original->isPublished() : TRUE;
+    if (DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => $entity->getOriginal() !== NULL, fn() => isset($entity->original))) {
+      $was_published = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => $entity->getOriginal(), fn() => $entity->original) instanceof EntityPublishedInterface ? DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => $entity->getOriginal(), fn() => $entity->original)->isPublished() : TRUE;
     }
     else {
       $was_published = FALSE;
@@ -265,11 +266,11 @@ class CacheInvalidator {
     $tags = [];
     if ($is_published && $was_published) {
       // Update published: only the modified field values.
-      $tags = $this->getPublishedEntityFieldsCacheTags($entity, $entity->original);
+      $tags = $this->getPublishedEntityFieldsCacheTags($entity, DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => $entity->getOriginal(), fn() => $entity->original));
     }
     elseif ($was_published) {
       // Update and unpublish: all non-empty original field values.
-      $tags = $this->getPublishedEntityFieldsCacheTags($entity->original);
+      $tags = $this->getPublishedEntityFieldsCacheTags(DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => $entity->getOriginal(), fn() => $entity->original));
     }
     elseif ($is_published) {
       // Insert or delete published, update and publish: all non-empty field
