diff --git a/entity_pdf.module b/entity_pdf.module
index e777190..7d84c66 100644
--- a/entity_pdf.module
+++ b/entity_pdf.module
@@ -4,7 +4,8 @@
  * @file
  * Entity PDF Module.
  */
-
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\entity_pdf\Hook\EntityPdfHooks;
 use Drupal\Core\Link;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Url;
@@ -12,42 +13,18 @@ use Drupal\Core\Url;
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function entity_pdf_help($route_name, RouteMatchInterface $route_match) {
-  switch ($route_name) {
-    case 'help.page.entity_pdf':
-      $output = '';
-      $output .= '<h3>' . t('About') . '</h3>';
-      $output .= '<p>' . t('Entity PDF can create a PDF from any entity based on any View mode.') . '</p>';
-      $output .= '<p>' . t('For more info: @readme', [
-        '@readme' => Link::fromTextAndUrl(t('Readme.md'), Url::fromUri('https://git.drupalcode.org/project/entity_pdf/-/raw/HEAD/README.md?plain=1', ['attributes' => ['target' => '_blank']]))->toString(),
-      ]) . '</p>';
-      return $output;
-  }
+  return \Drupal::service(EntityPdfHooks::class)->help($route_name, $route_match);
 }
 
 /**
  * Implements hook_theme().
  */
-function entity_pdf_theme($existing, $type, $theme, $path) {
-  $config = \Drupal::configFactory()->get('entity_pdf.settings');
-  $path = $config->get('customPdfTemplatePath');
-  $theme = [
-    'htmlpdf' => [
-      'variables' => [
-        'title' => NULL,
-        'content' => [],
-        'base_url' => NULL,
-        'langcode' => NULL,
-      ],
-    ],
-  ];
-
-  // If a custom pdf template path to the htmlpdf.html.twig file has been set,
-  // and that file really exists, then add the 'path' property and use it.
-  if (!empty($path) && file_exists(DRUPAL_ROOT . '/' . $path . '/htmlpdf.html.twig')) {
-    $theme['htmlpdf']['path'] = $path;
-  }
-  return $theme;
+#[LegacyHook]
+function entity_pdf_theme($existing, $type, $theme, $path)
+{
+    return \Drupal::service(EntityPdfHooks::class)->theme($existing, $type, $theme, $path);
 }
 
 /**
@@ -58,35 +35,8 @@ function entity_pdf_theme($existing, $type, $theme, $path) {
  * @todo This should be removed once https://www.drupal.org/project/drupal/issues/2282159
  * is in Drupal core because it does the exact same thing as the patch. (#21)
  */
-function entity_pdf_theme_suggestions_field_alter(&$suggestions, $variables, $hook) {
-  $view_mode = $variables['element']['#view_mode'];
-  if ($view_mode == '_custom' && isset($variables['element']['#ds_view_mode'])) {
-    $view_mode = $variables['element']['#ds_view_mode'];
-  }
-
-  // Check if the patch has been applied, or if the patch was incorporated into
-  // Drupal core already. Add suggestions if that is not the case.
-  if (!in_array('field__' . $view_mode, $suggestions)) {
-    $new_suggestions = [];
-    foreach ($suggestions as $suggestion) {
-      $new_suggestions[] = $suggestion;
-      if ($suggestion == 'field__' . $variables['element']['#field_name']) {
-        $new_suggestions[] = 'field__' . $view_mode;
-        $new_suggestions[] = 'field__' . $view_mode . '__' . $variables['element']['#bundle'];
-        $new_suggestions[] = 'field__' . $view_mode . '__' . $variables['element']['#field_name'];
-      }
-      if ($suggestion == 'field__' . $variables['element']['#entity_type'] . '__' . $variables['element']['#field_name'] . '__' . $variables['element']['#bundle']) {
-        $new_suggestions[] = 'field__' . $variables['element']['#entity_type'] . '__' . $view_mode . '__' . $variables['element']['#bundle'];
-        $new_suggestions[] = 'field__' . $variables['element']['#entity_type'] . '__' . $view_mode . '__' . $variables['element']['#field_name'];
-        $new_suggestions[] = 'field__' . $variables['element']['#entity_type'] . '__' . $view_mode . '__' . $variables['element']['#field_name'] . '__' . $variables['element']['#bundle'];
-      }
-    }
-    $suggestions = $new_suggestions;
-  }
-
-  // @deprecated: This suggestion should not be used anymore. It is merely here
-  // because it was in a previous release.
-  // @todo remove it before going to stable release and inform in release notes
-  // to not use this anymore.
-  $suggestions[] = 'field__' . $variables['element']['#field_name'] . '__' . $view_mode;
+#[LegacyHook]
+function entity_pdf_theme_suggestions_field_alter(&$suggestions, $variables, $hook)
+{
+    \Drupal::service(EntityPdfHooks::class)->themeSuggestionsFieldAlter($suggestions, $variables, $hook);
 }
diff --git a/entity_pdf.services.yml b/entity_pdf.services.yml
index 4e0323b..7192dd6 100644
--- a/entity_pdf.services.yml
+++ b/entity_pdf.services.yml
@@ -5,3 +5,7 @@ services:
   plugin.manager.entity_pdf_rendering_engine:
     class: Drupal\entity_pdf\Plugin\EntityPdfRenderingEngineManager
     parent: default_plugin_manager
+
+  Drupal\entity_pdf\Hook\EntityPdfHooks:
+    class: Drupal\entity_pdf\Hook\EntityPdfHooks
+    autowire: true
diff --git a/src/Hook/EntityPdfHooks.php b/src/Hook/EntityPdfHooks.php
new file mode 100644
index 0000000..a6420b0
--- /dev/null
+++ b/src/Hook/EntityPdfHooks.php
@@ -0,0 +1,104 @@
+<?php
+
+namespace Drupal\entity_pdf\Hook;
+
+use Drupal\Core\Link;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Url;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+/**
+ * Hook implementations for entity_pdf.
+ */
+class EntityPdfHooks
+{
+    use StringTranslationTrait;
+    /**
+     * Implements hook_help().
+     */
+    #[Hook('help')]
+    public function help($route_name, \Drupal\Core\Routing\RouteMatchInterface $route_match)
+    {
+        switch ($route_name) {
+            case 'help.page.entity_pdf':
+                $output = '';
+                $output .= '<h3>' . $this->t('About') . '</h3>';
+                $output .= '<p>' . $this->t('Entity PDF can create a PDF from any entity based on any View mode.') . '</p>';
+                $output .= '<p>' . $this->t('For more info: @readme', [
+                    '@readme' => \Drupal\Core\Link::fromTextAndUrl($this->t('Readme.md'), \Drupal\Core\Url::fromUri('https://git.drupalcode.org/project/entity_pdf/-/raw/HEAD/README.md?plain=1', [
+                        'attributes' => [
+                            'target' => '_blank',
+                        ],
+                    ]))->toString(),
+                ]) . '</p>';
+                return $output;
+        }
+    }
+    /**
+     * Implements hook_theme().
+     */
+    #[Hook('theme')]
+    public static function theme($existing, $type, $theme, $path)
+    {
+        $config = \Drupal::configFactory()->get('entity_pdf.settings');
+        $path = $config->get('customPdfTemplatePath');
+        $theme = [
+            'htmlpdf' => [
+                'variables' => [
+                    'title' => NULL,
+                    'content' => [
+                    ],
+                    'base_url' => NULL,
+                    'langcode' => NULL,
+                ],
+            ],
+        ];
+        // If a custom pdf template path to the htmlpdf.html.twig file has been set,
+        // and that file really exists, then add the 'path' property and use it.
+        if (!empty($path) && file_exists(DRUPAL_ROOT . '/' . $path . '/htmlpdf.html.twig')) {
+            $theme['htmlpdf']['path'] = $path;
+        }
+        return $theme;
+    }
+    /**
+     * Implements hook_theme_suggestions_HOOK_alter().
+     *
+     * Add view_mode suggestions to all field templates based on the view mode.
+     *
+     * @todo This should be removed once https://www.drupal.org/project/drupal/issues/2282159
+     * is in Drupal core because it does the exact same thing as the patch. (#21)
+     */
+    #[Hook('theme_suggestions_field_alter')]
+    public static function themeSuggestionsFieldAlter(&$suggestions, $variables, $hook)
+    {
+        $view_mode = $variables['element']['#view_mode'];
+        if ($view_mode == '_custom' && isset($variables['element']['#ds_view_mode'])) {
+            $view_mode = $variables['element']['#ds_view_mode'];
+        }
+        // Check if the patch has been applied, or if the patch was incorporated into
+        // Drupal core already. Add suggestions if that is not the case.
+        if (!in_array('field__' . $view_mode, $suggestions)) {
+            $new_suggestions = [
+            ];
+            foreach ($suggestions as $suggestion) {
+                $new_suggestions[] = $suggestion;
+                if ($suggestion == 'field__' . $variables['element']['#field_name']) {
+                    $new_suggestions[] = 'field__' . $view_mode;
+                    $new_suggestions[] = 'field__' . $view_mode . '__' . $variables['element']['#bundle'];
+                    $new_suggestions[] = 'field__' . $view_mode . '__' . $variables['element']['#field_name'];
+                }
+                if ($suggestion == 'field__' . $variables['element']['#entity_type'] . '__' . $variables['element']['#field_name'] . '__' . $variables['element']['#bundle']) {
+                    $new_suggestions[] = 'field__' . $variables['element']['#entity_type'] . '__' . $view_mode . '__' . $variables['element']['#bundle'];
+                    $new_suggestions[] = 'field__' . $variables['element']['#entity_type'] . '__' . $view_mode . '__' . $variables['element']['#field_name'];
+                    $new_suggestions[] = 'field__' . $variables['element']['#entity_type'] . '__' . $view_mode . '__' . $variables['element']['#field_name'] . '__' . $variables['element']['#bundle'];
+                }
+            }
+            $suggestions = $new_suggestions;
+        }
+        // @deprecated: This suggestion should not be used anymore. It is merely here
+        // because it was in a previous release.
+        // @todo remove it before going to stable release and inform in release notes
+        // to not use this anymore.
+        $suggestions[] = 'field__' . $variables['element']['#field_name'] . '__' . $view_mode;
+    }
+}
