diff --git a/module_instructions.module b/module_instructions.module
index 5cddeef..d1b9318 100644
--- a/module_instructions.module
+++ b/module_instructions.module
@@ -5,6 +5,8 @@
  * Contains module_instructions.module.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\module_instructions\Hook\ModuleInstructionsHooks;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Url;
@@ -12,64 +14,25 @@ use Drupal\Core\Url;
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function module_instructions_help($route_name, RouteMatchInterface $route_match) {
-  switch ($route_name) {
-    case 'help.page.module_instructions':
-      $output = '';
-      $output .= '<h3>' . t('About') . '</h3>';
-      $output .= '<p>' . t('Module Instructions module shows the content of README.txt, CHANGELOG.txt and LICENSE.txt files for contrib modules.') . '</p>';
-      return $output;
-  }
+  return \Drupal::service(ModuleInstructionsHooks::class)->help($route_name, $route_match);
 }
 
 /**
  * Implements hook_module_instructions_info().
  */
+#[LegacyHook]
 function module_instructions_module_instructions_info() {
-  return [
-    'readme' => [
-      'label' => t('Readme'),
-    ],
-    'license' => [
-      'label' => t('License'),
-    ],
-    'changelog' => [
-      'label' => t('Changelog'),
-    ],
-  ];
+  return \Drupal::service(ModuleInstructionsHooks::class)->moduleInstructionsInfo();
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function module_instructions_form_system_modules_alter(&$form, FormStateInterface $form_state, $form_id) {
-  if (!\Drupal::currentUser()->hasPermission('access module instruction files')) {
-    return;
-  }
-
-  /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */
-  $module_handler = \Drupal::service('module_handler');
-  $files = $module_handler->invokeAll('module_instructions_info');
-
-  /** @var \Drupal\Core\Config\ImmutableConfig $config */
-  $config = \Drupal::config('module_instructions.settings');
-  $types = $config->get('types');
-
-  $collection = \Drupal::state()->get('module_instructions_collection');
-  foreach ($form['modules'] as $name => $group) {
-    if (is_array($group)) {
-      foreach ($group as $module => $item) {
-        if (is_array($item) && !empty($item['name']) && !empty($collection[$module])) {
-          foreach ($collection[$module] as $type => $file) {
-            if ($collection[$module][$type] && isset($types[$type])) {
-              $label = !empty($files[$type]['label']) ? $files[$type]['label'] : ucfirst($type);
-              $form['modules'][$name][$module]['links'][$type] = _module_instructions_create_link_array($module, $label, $file);
-            }
-          }
-        }
-      }
-    }
-  }
+  \Drupal::service(ModuleInstructionsHooks::class)->formSystemModulesAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -107,83 +70,41 @@ function _module_instructions_create_link_array($module, $label, $file) {
 /**
  * Implements hook_theme_registry_alter().
  */
+#[LegacyHook]
 function module_instructions_theme_registry_alter(&$theme_registry) {
-  if (!empty($theme_registry['system_modules_details']) &&
-    \Drupal::currentUser()->hasPermission('access module instruction files')
-  ) {
-    $path = \Drupal::service('extension.path.resolver')->getPath('module', 'module_instructions') . '/templates';
-    $theme_registry['system_modules_details']['path'] = $path;
-  }
+  \Drupal::service(ModuleInstructionsHooks::class)->themeRegistryAlter($theme_registry);
 }
 
 /**
  * Implements hook_preprocess_HOOK().
  */
+#[LegacyHook]
 function module_instructions_preprocess_system_modules_details(&$variables) {
-  /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */
-  $module_handler = \Drupal::service('module_handler');
-  $files = $module_handler->invokeAll('module_instructions_info');
-
-  $variables['module_links'] = array_merge(['help', 'permissions', 'configure'], array_keys($files));
+  \Drupal::service(ModuleInstructionsHooks::class)->preprocessSystemModulesDetails($variables);
 }
 
 /**
  * Implements hook_cron().
  */
+#[LegacyHook]
 function module_instructions_cron() {
-  /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */
-  $module_handler = \Drupal::service('module_handler');
-  $modules = $module_handler->getModuleList();
-
-  // Get all files.
-  $files = $module_handler->invokeAll('module_instructions_info');
-
-  $collection = [];
-  foreach ($modules as $module => $info) {
-    $collection[$module] = [];
-
-    foreach ($files as $file => $data) {
-      $collection[$module][strtolower($file)] = module_instructions_file_exists($module, $file);
-    }
-  }
-
-  \Drupal::state()->set('module_instructions_collection', $collection);
+  \Drupal::service(ModuleInstructionsHooks::class)->cron();
 }
 
 /**
  * Implements hook_modules_installed().
  */
+#[LegacyHook]
 function module_instructions_modules_installed($modules, $is_syncing) {
-  $collection = \Drupal::state()->get('module_instructions_collection');
-
-  /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */
-  $module_handler = \Drupal::service('module_handler');
-  $files = $module_handler->invokeAll('module_instructions_info');
-
-  foreach ($modules as $module) {
-    $collection[$module] = [];
-
-    foreach ($files as $file => $data) {
-      $collection[$module][strtolower($file)] = module_instructions_file_exists($module, $file);
-    }
-  }
-
-  \Drupal::state()->set('module_instructions_collection', $collection);
+  \Drupal::service(ModuleInstructionsHooks::class)->modulesInstalled($modules, $is_syncing);
 }
 
 /**
  * Implements hook_modules_uninstalled().
  */
+#[LegacyHook]
 function module_instructions_modules_uninstalled($modules, $is_syncing) {
-  $collection = \Drupal::state()->get('module_instructions_collection');
-
-  foreach ($modules as $module) {
-    if (isset($collection[$module])) {
-      unset($collection[$module]);
-    }
-  }
-
-  \Drupal::state()->set('module_instructions_collection', $collection);
+  \Drupal::service(ModuleInstructionsHooks::class)->modulesUninstalled($modules, $is_syncing);
 }
 
 /**
diff --git a/module_instructions.services.yml b/module_instructions.services.yml
new file mode 100644
index 0000000..df26e49
--- /dev/null
+++ b/module_instructions.services.yml
@@ -0,0 +1,5 @@
+
+services:
+  Drupal\module_instructions\Hook\ModuleInstructionsHooks:
+    class: Drupal\module_instructions\Hook\ModuleInstructionsHooks
+    autowire: true
diff --git a/src/Controller/ModuleInstructionsController.php b/src/Controller/ModuleInstructionsController.php
index 7ced161..07109ed 100644
--- a/src/Controller/ModuleInstructionsController.php
+++ b/src/Controller/ModuleInstructionsController.php
@@ -79,7 +79,7 @@ class ModuleInstructionsController extends ControllerBase {
       $this->moduleHandler->alter('module_instructions_pre_view', $content, $context);
 
       return [
-        '#markup' => '<pre>' . check_markup(_filter_url($content, $filter), $context['format']) . '</pre>',
+        '#markup' => '<pre>' . ['#type' => 'processed_text', '#text' => _filter_url($content, $filter), '#format' => $context['format']] . '</pre>',
       ];
     }
 
diff --git a/src/Hook/ModuleInstructionsHooks.php b/src/Hook/ModuleInstructionsHooks.php
new file mode 100644
index 0000000..d044e1e
--- /dev/null
+++ b/src/Hook/ModuleInstructionsHooks.php
@@ -0,0 +1,157 @@
+<?php
+
+namespace Drupal\module_instructions\Hook;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for module_instructions.
+ */
+class ModuleInstructionsHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public function help($route_name, RouteMatchInterface $route_match) {
+    switch ($route_name) {
+      case 'help.page.module_instructions':
+        $output = '';
+        $output .= '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<p>' . $this->t('Module Instructions module shows the content of README.txt, CHANGELOG.txt and LICENSE.txt files for contrib modules.') . '</p>';
+        return $output;
+    }
+  }
+
+  /**
+   * Implements hook_module_instructions_info().
+   */
+  #[Hook('module_instructions_info')]
+  public function moduleInstructionsInfo() {
+    return [
+      'readme' => [
+        'label' => $this->t('Readme'),
+      ],
+      'license' => [
+        'label' => $this->t('License'),
+      ],
+      'changelog' => [
+        'label' => $this->t('Changelog'),
+      ],
+    ];
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_system_modules_alter')]
+  public static function formSystemModulesAlter(&$form, FormStateInterface $form_state, $form_id) {
+    if (!\Drupal::currentUser()->hasPermission('access module instruction files')) {
+      return;
+    }
+    /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */
+    $module_handler = \Drupal::service('module_handler');
+    $files = $module_handler->invokeAll('module_instructions_info');
+    /** @var \Drupal\Core\Config\ImmutableConfig $config */
+    $config = \Drupal::config('module_instructions.settings');
+    $types = $config->get('types');
+    $collection = \Drupal::state()->get('module_instructions_collection');
+    foreach ($form['modules'] as $name => $group) {
+      if (is_array($group)) {
+        foreach ($group as $module => $item) {
+          if (is_array($item) && !empty($item['name']) && !empty($collection[$module])) {
+            foreach ($collection[$module] as $type => $file) {
+              if ($collection[$module][$type] && isset($types[$type])) {
+                $label = !empty($files[$type]['label']) ? $files[$type]['label'] : ucfirst($type);
+                $form['modules'][$name][$module]['links'][$type] = _module_instructions_create_link_array($module, $label, $file);
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Implements hook_theme_registry_alter().
+   */
+  #[Hook('theme_registry_alter')]
+  public static function themeRegistryAlter(&$theme_registry) {
+    if (!empty($theme_registry['system_modules_details']) && \Drupal::currentUser()->hasPermission('access module instruction files')) {
+      $path = \Drupal::service('extension.path.resolver')->getPath('module', 'module_instructions') . '/templates';
+      $theme_registry['system_modules_details']['path'] = $path;
+    }
+  }
+
+  /**
+   * Implements hook_preprocess_HOOK().
+   */
+  #[Hook('preprocess_system_modules_details')]
+  public static function preprocessSystemModulesDetails(&$variables) {
+    /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */
+    $module_handler = \Drupal::service('module_handler');
+    $files = $module_handler->invokeAll('module_instructions_info');
+    $variables['module_links'] = array_merge([
+      'help',
+      'permissions',
+      'configure',
+    ], array_keys($files));
+  }
+
+  /**
+   * Implements hook_cron().
+   */
+  #[Hook('cron')]
+  public static function cron() {
+    /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */
+    $module_handler = \Drupal::service('module_handler');
+    $modules = $module_handler->getModuleList();
+    // Get all files.
+    $files = $module_handler->invokeAll('module_instructions_info');
+    $collection = [];
+    foreach ($modules as $module => $info) {
+      $collection[$module] = [];
+      foreach ($files as $file => $data) {
+        $collection[$module][strtolower($file)] = module_instructions_file_exists($module, $file);
+      }
+    }
+    \Drupal::state()->set('module_instructions_collection', $collection);
+  }
+
+  /**
+   * Implements hook_modules_installed().
+   */
+  #[Hook('modules_installed')]
+  public static function modulesInstalled($modules, $is_syncing) {
+    $collection = \Drupal::state()->get('module_instructions_collection');
+    /** @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler */
+    $module_handler = \Drupal::service('module_handler');
+    $files = $module_handler->invokeAll('module_instructions_info');
+    foreach ($modules as $module) {
+      $collection[$module] = [];
+      foreach ($files as $file => $data) {
+        $collection[$module][strtolower($file)] = module_instructions_file_exists($module, $file);
+      }
+    }
+    \Drupal::state()->set('module_instructions_collection', $collection);
+  }
+
+  /**
+   * Implements hook_modules_uninstalled().
+   */
+  #[Hook('modules_uninstalled')]
+  public static function modulesUninstalled($modules, $is_syncing) {
+    $collection = \Drupal::state()->get('module_instructions_collection');
+    foreach ($modules as $module) {
+      if (isset($collection[$module])) {
+        unset($collection[$module]);
+      }
+    }
+    \Drupal::state()->set('module_instructions_collection', $collection);
+  }
+
+}
diff --git a/tests/src/Functional/ModuleInstructionsTests.php b/tests/src/Functional/ModuleInstructionsTests.php
index 46b27f9..c0bd4c3 100644
--- a/tests/src/Functional/ModuleInstructionsTests.php
+++ b/tests/src/Functional/ModuleInstructionsTests.php
@@ -2,11 +2,13 @@
 
 namespace Drupal\Tests\module_instructions\Functional;
 
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\BrowserTestBase;
 
 /**
  * Class ModuleInstructionsTests. The base class for testing instructions.
  */
+#[RunTestsInSeparateProcesses]
 class ModuleInstructionsTests extends BrowserTestBase {
 
   /**
