diff --git a/core/.phpstan-baseline.php b/core/.phpstan-baseline.php
index 13fa6dc2a554fd1753dd15de80c323774f332c7d..97c1d762b81b3375a20bb5b46d2e59fbc73357fc 100644
--- a/core/.phpstan-baseline.php
+++ b/core/.phpstan-baseline.php
@@ -23745,12 +23745,6 @@
 	'count' => 1,
 	'path' => __DIR__ . '/modules/locale/tests/src/Unit/Menu/LocaleLocalTasksTest.php',
 ];
-$ignoreErrors[] = [
-	'message' => '#^Variable \\$error in empty\\(\\) always exists and is not falsy\\.$#',
-	'identifier' => 'empty.variable',
-	'count' => 1,
-	'path' => __DIR__ . '/modules/media/media.install',
-];
 $ignoreErrors[] = [
 	'message' => '#^Method Drupal\\\\media\\\\Controller\\\\MediaFilterController\\:\\:checkCsrf\\(\\) has no return type specified\\.$#',
 	'identifier' => 'missingType.return',
diff --git a/core/modules/layout_discovery/layout_discovery.install b/core/modules/layout_discovery/layout_discovery.install
deleted file mode 100644
index 28222bc093ba8d229d844b9ed7dd3e436bfc24ba..0000000000000000000000000000000000000000
--- a/core/modules/layout_discovery/layout_discovery.install
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-/**
- * @file
- * Install, update, and uninstall functions for the Layout Discovery module.
- */
-
-/**
- * Implements hook_requirements().
- */
-function layout_discovery_requirements($phase): array {
-  $requirements = [];
-  if ($phase === 'install') {
-    if (\Drupal::moduleHandler()->moduleExists('layout_plugin')) {
-      $requirements['layout_discovery'] = [
-        'description' => t('Layout Discovery cannot be installed because the Layout Plugin module is installed and incompatible.'),
-        'severity' => REQUIREMENT_ERROR,
-      ];
-    }
-  }
-  return $requirements;
-}
diff --git a/core/modules/layout_discovery/src/Install/Requirements/LayoutDiscoveryRequirements.php b/core/modules/layout_discovery/src/Install/Requirements/LayoutDiscoveryRequirements.php
new file mode 100644
index 0000000000000000000000000000000000000000..d6b8ef7fc24d53641066c0115d5f336d7adba2e9
--- /dev/null
+++ b/core/modules/layout_discovery/src/Install/Requirements/LayoutDiscoveryRequirements.php
@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\layout_discovery\Install\Requirements;
+
+use Drupal\Core\Extension\InstallRequirementsInterface;
+
+/**
+ * Install time requirements for the layout_discovery module.
+ */
+class LayoutDiscoveryRequirements implements InstallRequirementsInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getRequirements(): array {
+    $requirements = [];
+    if (\Drupal::moduleHandler()->moduleExists('layout_plugin')) {
+      $requirements['layout_discovery'] = [
+        'description' => t('Layout Discovery cannot be installed because the Layout Plugin module is installed and incompatible.'),
+        'severity' => REQUIREMENT_ERROR,
+      ];
+    }
+    return $requirements;
+  }
+
+}
diff --git a/core/modules/media/media.install b/core/modules/media/media.install
index f0acbf482da43f9231e14201e5c89803db7c507e..417d7f7cb54c99f793f861f0691d657afdfc2296 100644
--- a/core/modules/media/media.install
+++ b/core/modules/media/media.install
@@ -9,106 +9,8 @@
 use Drupal\Core\File\FileExists;
 use Drupal\Core\File\FileSystemInterface;
 use Drupal\Core\Hook\Attribute\StopProceduralHookScan;
-use Drupal\Core\StringTranslation\TranslatableMarkup;
-use Drupal\Core\Url;
-use Drupal\image\Plugin\Field\FieldType\ImageItem;
-use Drupal\media\Entity\MediaType;
 use Drupal\user\RoleInterface;
 
-/**
- * Implements hook_requirements().
- */
-function media_requirements($phase): array {
-  $requirements = [];
-  if ($phase == 'install') {
-    $destination = 'public://media-icons/generic';
-    \Drupal::service('file_system')->prepareDirectory($destination, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
-    $is_writable = is_writable($destination);
-    $is_directory = is_dir($destination);
-    if (!$is_writable || !$is_directory) {
-      if (!$is_directory) {
-        $error = t('The directory %directory does not exist.', ['%directory' => $destination]);
-      }
-      else {
-        $error = t('The directory %directory is not writable.', ['%directory' => $destination]);
-      }
-      $description = t('An automated attempt to create this directory failed, possibly due to a permissions problem. To proceed with the installation, either create the directory and modify its permissions manually or ensure that the installer has the permissions to create it automatically. For more information, see INSTALL.txt or the <a href=":handbook_url">online handbook</a>.', [':handbook_url' => 'https://www.drupal.org/server-permissions']);
-      if (!empty($error)) {
-        $description = $error . ' ' . $description;
-        $requirements['media']['description'] = $description;
-        $requirements['media']['severity'] = REQUIREMENT_ERROR;
-      }
-    }
-  }
-  elseif ($phase === 'runtime') {
-    $module_handler = \Drupal::service('module_handler');
-    foreach (MediaType::loadMultiple() as $type) {
-      // Load the default display.
-      $display = \Drupal::service('entity_display.repository')
-        ->getViewDisplay('media', $type->id());
-
-      // Check for missing source field definition.
-      $source_field_definition = $type->getSource()->getSourceFieldDefinition($type);
-      if (empty($source_field_definition)) {
-        $requirements['media_missing_source_field_' . $type->id()] = [
-          'title' => t('Media'),
-          'description' => t('The source field definition for the %type media type is missing.',
-            [
-              '%type' => $type->label(),
-            ]
-          ),
-          'severity' => REQUIREMENT_ERROR,
-        ];
-        continue;
-      }
-
-      // When a new media type with an image source is created we're
-      // configuring the default entity view display using the 'large' image
-      // style. Unfortunately, if a site builder has deleted the 'large' image
-      // style, we need some other image style to use, but at this point, we
-      // can't really know the site builder's intentions. So rather than do
-      // something surprising, we're leaving the embedded media without an
-      // image style and adding a warning that the site builder might want to
-      // add an image style.
-      // @see Drupal\media\Plugin\media\Source\Image::prepareViewDisplay
-      if (!is_a($source_field_definition->getItemDefinition()->getClass(), ImageItem::class, TRUE)) {
-        continue;
-      }
-
-      $component = $display->getComponent($source_field_definition->getName());
-      if (empty($component) || $component['type'] !== 'image' || !empty($component['settings']['image_style'])) {
-        continue;
-      }
-
-      $action_item = '';
-      if ($module_handler->moduleExists('field_ui') && \Drupal::currentUser()->hasPermission('administer media display')) {
-        $url = Url::fromRoute('entity.entity_view_display.media.default', [
-          'media_type' => $type->id(),
-        ])->toString();
-        $action_item = new TranslatableMarkup('If you would like to change this, <a href=":display">add an image style to the %field_name field</a>.',
-          [
-            '%field_name' => $source_field_definition->label(),
-            ':display' => $url,
-          ]);
-      }
-      $requirements['media_default_image_style_' . $type->id()] = [
-        'title' => t('Media'),
-        'description' => new TranslatableMarkup('The default display for the %type media type is not currently using an image style on the %field_name field. Not using an image style can lead to much larger file downloads. @action_item',
-          [
-            '%field_name' => $source_field_definition->label(),
-            '@action_item' => $action_item,
-            '%type' => $type->label(),
-          ]
-        ),
-        'severity' => REQUIREMENT_WARNING,
-      ];
-    }
-
-  }
-
-  return $requirements;
-}
-
 /**
  * Implements hook_install().
  */
diff --git a/core/modules/media/src/Hook/MediaRequirementsHooks.php b/core/modules/media/src/Hook/MediaRequirementsHooks.php
new file mode 100644
index 0000000000000000000000000000000000000000..61d39c604ebef1f00118ab7a333189303cd20581
--- /dev/null
+++ b/core/modules/media/src/Hook/MediaRequirementsHooks.php
@@ -0,0 +1,98 @@
+<?php
+
+namespace Drupal\media\Hook;
+
+use Drupal\Core\Entity\EntityDisplayRepositoryInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\Url;
+use Drupal\image\Plugin\Field\FieldType\ImageItem;
+use Drupal\media\Entity\MediaType;
+
+/**
+ * Hook implementations for media.
+ */
+class MediaRequirementsHooks {
+
+  use StringTranslationTrait;
+
+  public function __construct(
+    protected readonly AccountInterface $currentUser,
+    protected readonly ModuleHandlerInterface $moduleHandler,
+    protected readonly EntityDisplayRepositoryInterface $entityDisplayRepository,
+  ) {}
+
+  /**
+   * Implements hook_runtime_requirements().
+   */
+  #[Hook('runtime_requirements')]
+  public function runtime(): array {
+    $requirements = [];
+    foreach (MediaType::loadMultiple() as $type) {
+      // Load the default display.
+      $display = $this->entityDisplayRepository->getViewDisplay('media', $type->id());
+
+      // Check for missing source field definition.
+      $source_field_definition = $type->getSource()->getSourceFieldDefinition($type);
+      if (empty($source_field_definition)) {
+        $requirements['media_missing_source_field_' . $type->id()] = [
+          'title' => $this->t('Media'),
+          'description' => $this->t('The source field definition for the %type media type is missing.',
+            [
+              '%type' => $type->label(),
+            ]
+          ),
+          'severity' => REQUIREMENT_ERROR,
+        ];
+        continue;
+      }
+
+      // When a new media type with an image source is created we're
+      // configuring the default entity view display using the 'large' image
+      // style. Unfortunately, if a site builder has deleted the 'large' image
+      // style, we need some other image style to use, but at this point, we
+      // can't really know the site builder's intentions. So rather than do
+      // something surprising, we're leaving the embedded media without an
+      // image style and adding a warning that the site builder might want to
+      // add an image style.
+      // @see Drupal\media\Plugin\media\Source\Image::prepareViewDisplay
+      if (!is_a($source_field_definition->getItemDefinition()->getClass(), ImageItem::class, TRUE)) {
+        continue;
+      }
+
+      $component = $display->getComponent($source_field_definition->getName());
+      if (empty($component) || $component['type'] !== 'image' || !empty($component['settings']['image_style'])) {
+        continue;
+      }
+
+      $action_item = '';
+      if ($this->moduleHandler->moduleExists('field_ui') && $this->currentUser->hasPermission('administer media display')) {
+        $url = Url::fromRoute('entity.entity_view_display.media.default', [
+          'media_type' => $type->id(),
+        ])->toString();
+        $action_item = new TranslatableMarkup('If you would like to change this, <a href=":display">add an image style to the %field_name field</a>.',
+          [
+            '%field_name' => $source_field_definition->label(),
+            ':display' => $url,
+          ]);
+      }
+      $requirements['media_default_image_style_' . $type->id()] = [
+        'title' => $this->t('Media'),
+        'description' => new TranslatableMarkup('The default display for the %type media type is not currently using an image style on the %field_name field. Not using an image style can lead to much larger file downloads. @action_item',
+          [
+            '%field_name' => $source_field_definition->label(),
+            '@action_item' => $action_item,
+            '%type' => $type->label(),
+          ]
+        ),
+        'severity' => REQUIREMENT_WARNING,
+      ];
+    }
+
+    return $requirements;
+  }
+
+}
diff --git a/core/modules/media/src/Install/Requirements/MediaRequirements.php b/core/modules/media/src/Install/Requirements/MediaRequirements.php
new file mode 100644
index 0000000000000000000000000000000000000000..a69a79aaf81128217e202286ddee3c5ebf26e76a
--- /dev/null
+++ b/core/modules/media/src/Install/Requirements/MediaRequirements.php
@@ -0,0 +1,39 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\media\Install\Requirements;
+
+use Drupal\Core\Extension\InstallRequirementsInterface;
+use Drupal\Core\File\FileSystemInterface;
+
+/**
+ * Install time requirements for the media module.
+ */
+class MediaRequirements implements InstallRequirementsInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getRequirements(): array {
+    $requirements = [];
+    $destination = 'public://media-icons/generic';
+    \Drupal::service('file_system')->prepareDirectory($destination, FileSystemInterface::CREATE_DIRECTORY | FileSystemInterface::MODIFY_PERMISSIONS);
+    $is_writable = is_writable($destination);
+    $is_directory = is_dir($destination);
+    if (!$is_writable || !$is_directory) {
+      if (!$is_directory) {
+        $error = t('The directory %directory does not exist.', ['%directory' => $destination]);
+      }
+      else {
+        $error = t('The directory %directory is not writable.', ['%directory' => $destination]);
+      }
+      $description = t('An automated attempt to create this directory failed, possibly due to a permissions problem. To proceed with the installation, either create the directory and modify its permissions manually or ensure that the installer has the permissions to create it automatically. For more information, see INSTALL.txt or the <a href=":handbook_url">online handbook</a>.', [':handbook_url' => 'https://www.drupal.org/server-permissions']);
+      $description = $error . ' ' . $description;
+      $requirements['media']['description'] = $description;
+      $requirements['media']['severity'] = REQUIREMENT_ERROR;
+    }
+    return $requirements;
+  }
+
+}
diff --git a/core/modules/package_manager/package_manager.install b/core/modules/package_manager/package_manager.install
deleted file mode 100644
index 22d42a346ead7fc5b93e132b14b7f150bf057528..0000000000000000000000000000000000000000
--- a/core/modules/package_manager/package_manager.install
+++ /dev/null
@@ -1,82 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains install and update functions for Package Manager.
- */
-
-declare(strict_types=1);
-
-use Drupal\Core\Site\Settings;
-use Drupal\package_manager\ComposerInspector;
-use Drupal\package_manager\Exception\StageFailureMarkerException;
-use Drupal\package_manager\FailureMarker;
-use PhpTuf\ComposerStager\API\Exception\ExceptionInterface;
-use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface;
-
-/**
- * Implements hook_requirements().
- */
-function package_manager_requirements(string $phase): array {
-  $requirements = [];
-
-  if (Settings::get('testing_package_manager', FALSE) === FALSE) {
-    $requirements['testing_package_manager'] = [
-      'title' => 'Package Manager',
-      'description' => t("Package Manager is available for early testing. To install the module set the value of 'testing_package_manager' to TRUE in your settings.php file."),
-      'severity' => REQUIREMENT_ERROR,
-    ];
-    return $requirements;
-  }
-
-  // If we're able to check for the presence of the failure marker at all, do it
-  // irrespective of the current run phase. If the failure marker is there, the
-  // site is in an indeterminate state and should be restored from backup ASAP.
-  $service_id = FailureMarker::class;
-  if (\Drupal::hasService($service_id)) {
-    try {
-      \Drupal::service($service_id)->assertNotExists(NULL);
-    }
-    catch (StageFailureMarkerException $exception) {
-      $requirements['package_manager_failure_marker'] = [
-        'title' => t('Failed Package Manager update detected'),
-        'description' => $exception->getMessage(),
-        'severity' => REQUIREMENT_ERROR,
-      ];
-    }
-  }
-
-  if ($phase !== 'runtime') {
-    return $requirements;
-  }
-  /** @var \PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface $executable_finder */
-  $executable_finder = \Drupal::service(ExecutableFinderInterface::class);
-
-  // Report the Composer version in use, as well as its path.
-  $title = t('Composer version');
-  try {
-    $requirements['package_manager_composer'] = [
-      'title' => $title,
-      'description' => t('@version (<code>@path</code>)', [
-        '@version' => \Drupal::service(ComposerInspector::class)->getVersion(),
-        '@path' => $executable_finder->find('composer'),
-      ]),
-      'severity' => REQUIREMENT_INFO,
-    ];
-  }
-  catch (\Throwable $e) {
-    // All Composer Stager exceptions are translatable.
-    $message = $e instanceof ExceptionInterface
-      ? $e->getTranslatableMessage()
-      : $e->getMessage();
-
-    $requirements['package_manager_composer'] = [
-      'title' => $title,
-      'description' => t('Composer was not found. The error message was: @message', [
-        '@message' => $message,
-      ]),
-      'severity' => REQUIREMENT_ERROR,
-    ];
-  }
-  return $requirements;
-}
diff --git a/core/modules/package_manager/src/Hook/PackageManagerRequirementsHooks.php b/core/modules/package_manager/src/Hook/PackageManagerRequirementsHooks.php
new file mode 100644
index 0000000000000000000000000000000000000000..0ec0cb9293cf22f7e79f547d80a6faeb8b57d656
--- /dev/null
+++ b/core/modules/package_manager/src/Hook/PackageManagerRequirementsHooks.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace Drupal\package_manager\Hook;
+
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\package_manager\ComposerInspector;
+use Drupal\package_manager\Exception\StageFailureMarkerException;
+use Drupal\package_manager\FailureMarker;
+use PhpTuf\ComposerStager\API\Exception\ExceptionInterface;
+use PhpTuf\ComposerStager\API\Finder\Service\ExecutableFinderInterface;
+
+/**
+ * Hook implementations for media.
+ */
+class PackageManagerRequirementsHooks {
+
+  use StringTranslationTrait;
+
+  public function __construct(
+    protected readonly ComposerInspector $composerInspector,
+    protected ExecutableFinderInterface $executableFinder,
+  ) {}
+
+  /**
+   * Implements hook_runtime_requirements().
+   */
+  #[Hook('runtime_requirements')]
+  public function runtime(): array {
+    $requirements = $this->checkFailure();
+
+    // Report the Composer version in use, as well as its path.
+    $title = $this->t('Composer version');
+    try {
+      $requirements['package_manager_composer'] = [
+        'title' => $title,
+        'description' => $this->t('@version (<code>@path</code>)', [
+          '@version' => $this->composerInspector->getVersion(),
+          '@path' => $this->executableFinder->find('composer'),
+        ]),
+        'severity' => REQUIREMENT_INFO,
+      ];
+    }
+    catch (\Throwable $e) {
+      // All Composer Stager exceptions are translatable.
+      $message = $e instanceof ExceptionInterface
+        ? $e->getTranslatableMessage()
+        : $e->getMessage();
+
+      $requirements['package_manager_composer'] = [
+        'title' => $title,
+        'description' => $this->t('Composer was not found. The error message was: @message', [
+          '@message' => $message,
+        ]),
+        'severity' => REQUIREMENT_ERROR,
+      ];
+    }
+
+    return $requirements;
+  }
+
+  /**
+   * Implements hook_update_requirements().
+   */
+  #[Hook('update_requirements')]
+  public function update(): array {
+    return $this->checkFailure();
+  }
+
+  public function checkFailure(): array {
+    $requirements = [];
+    // If we're able to check for the presence of the failure marker at all, do it
+    // irrespective of the current run phase. If the failure marker is there, the
+    // site is in an indeterminate state and should be restored from backup ASAP.
+    $service_id = FailureMarker::class;
+    if (\Drupal::hasService($service_id)) {
+      try {
+        \Drupal::service($service_id)->assertNotExists(NULL);
+      }
+      catch (StageFailureMarkerException $exception) {
+        $requirements['package_manager_failure_marker'] = [
+          'title' => $this->t('Failed Package Manager update detected'),
+          'description' => $exception->getMessage(),
+          'severity' => REQUIREMENT_ERROR,
+        ];
+      }
+    }
+
+    return $requirements;
+  }
+
+}
diff --git a/core/modules/package_manager/src/Install/Requirements/PackageManagerRequirements.php b/core/modules/package_manager/src/Install/Requirements/PackageManagerRequirements.php
new file mode 100644
index 0000000000000000000000000000000000000000..30b9cb6395bee3eb1c679fdecf859a6f48f64ef5
--- /dev/null
+++ b/core/modules/package_manager/src/Install/Requirements/PackageManagerRequirements.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\package_manager\Install\Requirements;
+
+use Drupal\Core\Extension\InstallRequirementsInterface;
+use Drupal\Core\Site\Settings;
+
+/**
+ * Install time requirements for the package_manager module.
+ */
+class PackageManagerRequirements implements InstallRequirementsInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getRequirements(): array {
+    $requirements = [];
+
+    if (Settings::get('testing_package_manager', FALSE) === FALSE) {
+      $requirements['testing_package_manager'] = [
+        'title' => 'Package Manager',
+        'description' => t("Package Manager is available for early testing. To install the module set the value of 'testing_package_manager' to TRUE in your settings.php file."),
+        'severity' => REQUIREMENT_ERROR,
+      ];
+      return $requirements;
+    }
+
+    return $requirements;
+  }
+
+}
diff --git a/core/modules/pgsql/pgsql.install b/core/modules/pgsql/pgsql.install
index 3adeb4f5dff38e2dd107c4080d1c794e41fec21b..682ef7d605b567d81be47a226ef4ba83be8ccfb2 100644
--- a/core/modules/pgsql/pgsql.install
+++ b/core/modules/pgsql/pgsql.install
@@ -5,42 +5,6 @@
  * Install, update and uninstall functions for the pgsql module.
  */
 
-use Drupal\Core\Database\Database;
-
-/**
- * Implements hook_requirements().
- */
-function pgsql_requirements(): array {
-  $requirements = [];
-  // Test with PostgreSQL databases for the status of the pg_trgm extension.
-  if (Database::isActiveConnection()) {
-    $connection = Database::getConnection();
-
-    // Set the requirement just for postgres.
-    if ($connection->driver() == 'pgsql') {
-      $requirements['pgsql_extension_pg_trgm'] = [
-        'severity' => REQUIREMENT_OK,
-        'title' => t('PostgreSQL pg_trgm extension'),
-        'value' => t('Available'),
-        'description' => 'The pg_trgm PostgreSQL extension is present.',
-      ];
-
-      // If the extension is not available, set the requirement error.
-      if (!$connection->schema()->extensionExists('pg_trgm')) {
-        $requirements['pgsql_extension_pg_trgm']['severity'] = REQUIREMENT_ERROR;
-        $requirements['pgsql_extension_pg_trgm']['value'] = t('Not created');
-        $requirements['pgsql_extension_pg_trgm']['description'] = t('The <a href=":pg_trgm">pg_trgm</a> PostgreSQL extension is not present. The extension is required by Drupal 10 to improve performance when using PostgreSQL. See <a href=":requirements">Drupal database server requirements</a> for more information.', [
-          ':pg_trgm' => 'https://www.postgresql.org/docs/current/pgtrgm.html',
-          ':requirements' => 'https://www.drupal.org/docs/system-requirements/database-server-requirements',
-        ]);
-      }
-
-    }
-  }
-
-  return $requirements;
-}
-
 /**
  * Implements hook_update_last_removed().
  */
diff --git a/core/modules/pgsql/src/Hook/PgsqlRequirementsHooks.php b/core/modules/pgsql/src/Hook/PgsqlRequirementsHooks.php
new file mode 100644
index 0000000000000000000000000000000000000000..324102af7c9935b473dfd2fd238a053127c23f18
--- /dev/null
+++ b/core/modules/pgsql/src/Hook/PgsqlRequirementsHooks.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Drupal\pgsql\Hook;
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for pgsql module.
+ */
+class PgsqlRequirementsHooks {
+
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_runtime_requirements().
+   */
+  #[Hook('runtime_requirements')]
+  public function runtime(): array {
+    return $this->checkRequirements();
+  }
+
+  /**
+   * Implements hook_update_requirements().
+   */
+  #[Hook('update_requirements')]
+  public function update(): array {
+    return $this->checkRequirements();
+  }
+
+  public function checkRequirements(): array {
+    $requirements = [];
+    // Test with PostgreSQL databases for the status of the pg_trgm extension.
+    if (Database::isActiveConnection()) {
+      $connection = Database::getConnection();
+
+      // Set the requirement just for postgres.
+      if ($connection->driver() == 'pgsql') {
+        $requirements['pgsql_extension_pg_trgm'] = [
+          'severity' => REQUIREMENT_OK,
+          'title' => $this->t('PostgreSQL pg_trgm extension'),
+          'value' => $this->t('Available'),
+          'description' => 'The pg_trgm PostgreSQL extension is present.',
+        ];
+
+        // If the extension is not available, set the requirement error.
+        if (!$connection->schema()->extensionExists('pg_trgm')) {
+          $requirements['pgsql_extension_pg_trgm']['severity'] = REQUIREMENT_ERROR;
+          $requirements['pgsql_extension_pg_trgm']['value'] = $this->t('Not created');
+          $requirements['pgsql_extension_pg_trgm']['description'] = $this->t('The <a href=":pg_trgm">pg_trgm</a> PostgreSQL extension is not present. The extension is required by Drupal 10 to improve performance when using PostgreSQL. See <a href=":requirements">Drupal database server requirements</a> for more information.', [
+            ':pg_trgm' => 'https://www.postgresql.org/docs/current/pgtrgm.html',
+            ':requirements' => 'https://www.drupal.org/docs/system-requirements/database-server-requirements',
+          ]);
+        }
+
+      }
+    }
+
+    return $requirements;
+  }
+
+}
diff --git a/core/modules/pgsql/src/Requirements/PgsqlRequirements.php b/core/modules/pgsql/src/Requirements/PgsqlRequirements.php
new file mode 100644
index 0000000000000000000000000000000000000000..bebd027024240f5c3de415107624e8871558db35
--- /dev/null
+++ b/core/modules/pgsql/src/Requirements/PgsqlRequirements.php
@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\pgsql\Install\Requirements;
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\Extension\InstallRequirementsInterface;
+
+/**
+ * Install time requirements for the pgsql module.
+ */
+class PgsqlRequirements implements InstallRequirementsInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getRequirements(): array {
+    $requirements = [];
+    // Test with PostgreSQL databases for the status of the pg_trgm extension.
+    if (Database::isActiveConnection()) {
+      $connection = Database::getConnection();
+
+      // Set the requirement just for postgres.
+      if ($connection->driver() == 'pgsql') {
+        $requirements['pgsql_extension_pg_trgm'] = [
+          'severity' => REQUIREMENT_OK,
+          'title' => t('PostgreSQL pg_trgm extension'),
+          'value' => t('Available'),
+          'description' => 'The pg_trgm PostgreSQL extension is present.',
+        ];
+
+        // If the extension is not available, set the requirement error.
+        if (!$connection->schema()->extensionExists('pg_trgm')) {
+          $requirements['pgsql_extension_pg_trgm']['severity'] = REQUIREMENT_ERROR;
+          $requirements['pgsql_extension_pg_trgm']['value'] = t('Not created');
+          $requirements['pgsql_extension_pg_trgm']['description'] = t('The <a href=":pg_trgm">pg_trgm</a> PostgreSQL extension is not present. The extension is required by Drupal 10 to improve performance when using PostgreSQL. See <a href=":requirements">Drupal database server requirements</a> for more information.', [
+            ':pg_trgm' => 'https://www.postgresql.org/docs/current/pgtrgm.html',
+            ':requirements' => 'https://www.drupal.org/docs/system-requirements/database-server-requirements',
+          ]);
+        }
+
+      }
+    }
+
+    return $requirements;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/requirements1_test/requirements1_test.install b/core/modules/system/tests/modules/requirements1_test/requirements1_test.install
index 70767c16544df70325a1efe62529d330ce195b7c..fb84be133cd7c2ad6e12b674f6f05e9045f97c63 100644
--- a/core/modules/system/tests/modules/requirements1_test/requirements1_test.install
+++ b/core/modules/system/tests/modules/requirements1_test/requirements1_test.install
@@ -9,6 +9,8 @@
 
 /**
  * Implements hook_requirements().
+ *
+ * This tests the procedural implementations for this hook.
  */
 function requirements1_test_requirements($phase): array {
   $requirements = [];
diff --git a/core/modules/workspaces/src/Install/Requirements/WorkspacesRequirements.php b/core/modules/workspaces/src/Install/Requirements/WorkspacesRequirements.php
new file mode 100644
index 0000000000000000000000000000000000000000..a54148215af17e575783c1fb2a304eef24320f8e
--- /dev/null
+++ b/core/modules/workspaces/src/Install/Requirements/WorkspacesRequirements.php
@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\workspaces\Install\Requirements;
+
+use Drupal\Core\Extension\InstallRequirementsInterface;
+
+/**
+ * Install time requirements for the workspaces module.
+ */
+class WorkspacesRequirements implements InstallRequirementsInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getRequirements(): array {
+    $requirements = [];
+    if (\Drupal::moduleHandler()->moduleExists('workspace')) {
+      $requirements['workspace_incompatibility'] = [
+        'severity' => REQUIREMENT_ERROR,
+        'description' => t('Workspaces can not be installed when the contributed Workspace module is also installed. See the <a href=":link">upgrade path</a> page for more information on how to upgrade.', [
+          ':link' => 'https://www.drupal.org/node/2987783',
+        ]),
+      ];
+    }
+
+    return $requirements;
+  }
+
+}
diff --git a/core/modules/workspaces/workspaces.install b/core/modules/workspaces/workspaces.install
index a798b4a2c22bd4dec93e02cba513ac7e22cd3b55..6c9c99f29a3cae38c64b5c712e061065d0e1fc3c 100644
--- a/core/modules/workspaces/workspaces.install
+++ b/core/modules/workspaces/workspaces.install
@@ -8,25 +8,6 @@
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\workspaces\Entity\Workspace;
 
-/**
- * Implements hook_requirements().
- */
-function workspaces_requirements($phase): array {
-  $requirements = [];
-  if ($phase === 'install') {
-    if (\Drupal::moduleHandler()->moduleExists('workspace')) {
-      $requirements['workspace_incompatibility'] = [
-        'severity' => REQUIREMENT_ERROR,
-        'description' => t('Workspaces can not be installed when the contributed Workspace module is also installed. See the <a href=":link">upgrade path</a> page for more information on how to upgrade.', [
-          ':link' => 'https://www.drupal.org/node/2987783',
-        ]),
-      ];
-    }
-  }
-
-  return $requirements;
-}
-
 /**
  * Implements hook_install().
  */
diff --git a/core/profiles/tests/testing_requirements/src/Install/Requirements/TestingRequirementsRequirements.php b/core/profiles/tests/testing_requirements/src/Install/Requirements/TestingRequirementsRequirements.php
new file mode 100644
index 0000000000000000000000000000000000000000..71165b1c8dba16debd5272b8054cf6e6eebd899e
--- /dev/null
+++ b/core/profiles/tests/testing_requirements/src/Install/Requirements/TestingRequirementsRequirements.php
@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\testing_requirements\Install\Requirements;
+
+use Drupal\Core\Extension\InstallRequirementsInterface;
+
+/**
+ * Install time requirements for the testing_requirements module.
+ */
+class TestingRequirementsRequirements implements InstallRequirementsInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getRequirements(): array {
+    $requirements = [];
+    $requirements['testing_requirements'] = [
+      'title' => t('Testing requirements'),
+      'severity' => REQUIREMENT_ERROR,
+      'description' => t('Testing requirements failed requirements.'),
+    ];
+
+    return $requirements;
+  }
+
+}
diff --git a/core/profiles/tests/testing_requirements/testing_requirements.install b/core/profiles/tests/testing_requirements/testing_requirements.install
deleted file mode 100644
index 7cdb44d9d2999afa7b2be82560ff7336fa527997..0000000000000000000000000000000000000000
--- a/core/profiles/tests/testing_requirements/testing_requirements.install
+++ /dev/null
@@ -1,25 +0,0 @@
-<?php
-
-/**
- * @file
- * Install hooks for test profile.
- */
-
-declare(strict_types=1);
-
-/**
- * Implements hook_requirements().
- */
-function testing_requirements_requirements($phase): array {
-  $requirements = [];
-
-  if ($phase === 'install') {
-    $requirements['testing_requirements'] = [
-      'title' => t('Testing requirements'),
-      'severity' => REQUIREMENT_ERROR,
-      'description' => t('Testing requirements failed requirements.'),
-    ];
-  }
-
-  return $requirements;
-}