diff --git a/core/core.api.php b/core/core.api.php
index 0500a3c58aecebc87336db728c2818d51135bc86..0dbec836754ee5f821fb1ff8ff6003238b26ca54 100644
--- a/core/core.api.php
+++ b/core/core.api.php
@@ -1657,6 +1657,7 @@
  * Legacy meta hooks:
  * - hook_hook_info()
  * - hook_module_implements_alter()
+ * @see https://www.drupal.org/node/3496788
  *
  * Install hooks:
  * - hook_install()
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index cf42c65deeff28e02426e1517c203e3b91b998f3..f476ae2af8099d8d7e9572e055975662830601ba 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -6,7 +6,7 @@
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Extension\Exception\UnknownExtensionException;
 use Drupal\Core\Hook\Attribute\LegacyHook;
-use Drupal\Core\Hook\HookCollectorPass;
+use Drupal\Core\Hook\HookCollector;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 /**
@@ -61,7 +61,7 @@ class ModuleHandler implements ModuleHandlerInterface {
   /**
    * Hook and module keyed list of listeners.
    *
-   * @var array
+   * @var array<string, array<string, list<callable>>>
    */
   protected array $invokeMap = [];
 
@@ -70,21 +70,34 @@ class ModuleHandler implements ModuleHandlerInterface {
    *
    * @param string $root
    *   The app root.
-   * @param array $module_list
+   * @param array<string, array{type: string, pathname: string, filename: string}> $module_list
    *   An associative array whose keys are the names of installed modules and
    *   whose values are Extension class parameters. This is normally the
    *   %container.modules% parameter being set up by DrupalKernel.
    * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher
    *   The event dispatcher.
-   * @param array $hookImplementationsMap
+   * @param array<string, array<class-string, array<string, string>>> $hookImplementationsMap
    *   An array keyed by hook, classname, method and the value is the module.
-   * @param array $groupIncludes
-   *   An array of .inc files to get helpers from.
+   * @param array<string, list<string>> $groupIncludes
+   *   Lists of *.inc file paths that contain procedural implementations, keyed
+   *   by hook name.
+   * @param array<string, list<string>> $orderedExtraTypes
+   *   A multidimensional array of hooks that have been ordered and the
+   *   extra_types they have been ordered against. This is stored separately
+   *   from $hookImplementationsMap to prevent ordering again since this set
+   *   has already been fully ordered in HookCollectorPass.
    *
    * @see \Drupal\Core\DrupalKernel
    * @see \Drupal\Core\CoreServiceProvider
    */
-  public function __construct($root, array $module_list, protected EventDispatcherInterface $eventDispatcher, protected array $hookImplementationsMap, protected array $groupIncludes = []) {
+  public function __construct(
+    $root,
+    array $module_list,
+    protected EventDispatcherInterface $eventDispatcher,
+    protected array $hookImplementationsMap,
+    protected array $groupIncludes = [],
+    protected array $orderedExtraTypes = [],
+  ) {
     $this->root = $root;
     $this->moduleList = [];
     foreach ($module_list as $name => $module) {
@@ -192,7 +205,8 @@ protected function add($type, $name, $path) {
     $filename = file_exists($php_file_path) ? "$name.$type" : NULL;
     $this->moduleList[$name] = new Extension($this->root, $type, $pathname, $filename);
     $this->resetImplementations();
-    $hook_collector = HookCollectorPass::collectAllHookImplementations([$name => ['pathname' => $pathname]]);
+    $paths = [$name => ['pathname' => $pathname]];
+    $hook_collector = HookCollector::collectAllHookImplementations($paths);
     // A module freshly added will not be registered on the container yet.
     // ProceduralCall service does not yet know about it.
     // Note in HookCollectorPass:
@@ -427,27 +441,38 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
       $this->alterEventListeners[$cid] = [];
       $hook = $type . '_alter';
       $hook_listeners = $this->getHookListeners($hook);
+      $extra_modules = FALSE;
+      $extra_listeners = [];
       if (isset($extra_types)) {
-        // For multiple hooks, we need $modules to contain every module that
-        // implements at least one of them in the correct order.
-        foreach ($extra_types as $extra_type) {
-          foreach ($this->getHookListeners($extra_type . '_alter') as $module => $listeners) {
-            if (isset($hook_listeners[$module])) {
-              $hook_listeners[$module] = array_merge($hook_listeners[$module], $listeners);
-            }
-            else {
-              $hook_listeners[$module] = $listeners;
-              $extra_modules = TRUE;
-            }
+        $extra_hooks = array_map(static fn ($x) => $x . '_alter', $extra_types);
+        // First get the listeners implementing extra hooks.
+        foreach ($extra_hooks as $extra_hook) {
+          $hook_listeners = $this->findListenersForAlter($extra_hook, $hook_listeners, $extra_modules);
+        }
+        // Second, gather implementations ordered together. These are only used
+        // for ordering because the set might contain hooks not included in
+        // this alter() call. \Drupal\Core\Hook\HookPriority::change()
+        // registers the implementations of combined hooks.
+        foreach ([...$extra_hooks, $hook] as $extra_hook) {
+          if (isset($this->orderedExtraTypes[$extra_hook])) {
+            $orderedHooks = $this->orderedExtraTypes[$extra_hook];
+            $extra_listeners = $this->findListenersForAlter(implode(':', $orderedHooks));
+            // Remove already ordered hooks.
+            $extra_hooks = array_diff($extra_hooks, $orderedHooks);
           }
         }
       }
-      // If any modules implement one of the extra hooks that do not implement
-      // the primary hook, we need to add them to the $modules array in their
-      // appropriate order.
-      $modules = array_keys($hook_listeners);
-      if (isset($extra_modules)) {
-        $modules = $this->reOrderModulesForAlter($modules, $hook);
+      // If multiple alters were called, but they were already ordered by
+      // ordering attributes then keep that order.
+      if (isset($extra_hooks) && empty($extra_hooks)) {
+        $modules = array_keys(array_intersect_key($extra_listeners, $hook_listeners));
+      }
+      else {
+        // Otherwise, use a legacy ordering mechanism if needed.
+        $modules = array_keys($hook_listeners);
+        if ($extra_modules) {
+          $modules = $this->legacyReOrderModulesForAlter($modules, $hook);
+        }
       }
       foreach ($modules as $module) {
         foreach ($hook_listeners[$module] ?? [] as $listener) {
@@ -463,16 +488,16 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
   /**
    * Reorder modules for alters.
    *
-   * @param array $modules
+   * @param list<string> $modules
    *   A list of modules.
    * @param string $hook
-   *   The hook being worked on, for example form_alter.
+   *   The hook being worked on, for example 'form_alter'.
    *
-   * @return array
+   * @return list<string>
    *   The list, potentially reordered and changed by
    *   hook_module_implements_alter().
    */
-  protected function reOrderModulesForAlter(array $modules, string $hook): array {
+  protected function legacyReOrderModulesForAlter(array $modules, string $hook): array {
     // Order by module order first.
     $modules = array_intersect(array_keys($this->moduleList), $modules);
     // Alter expects the module list to be in the keys.
@@ -544,7 +569,7 @@ public function writeCache() {
    * @param string $hook
    *   The name of the hook.
    *
-   * @return array
+   * @return array<string, list<callable>>
    *   A list of event listeners implementing this hook.
    */
   protected function getHookListeners(string $hook): array {
@@ -572,7 +597,36 @@ protected function getHookListeners(string $hook): array {
         }
       }
     }
+
     return $this->invokeMap[$hook] ?? [];
   }
 
+  /**
+   * Helper to get hook listeners when in alter.
+   *
+   * @param string $hook
+   *   The extra hook or combination hook to check for.
+   * @param array<string, list<callable>> $hook_listeners
+   *   Hook listeners for the current hook_alter.
+   * @param bool|null $extra_modules
+   *   Whether there are extra modules to order.
+   *
+   * @return array<string, list<callable>>
+   *   The hook listeners.
+   */
+  public function findListenersForAlter(string $hook, array $hook_listeners = [], ?bool &$extra_modules = NULL): array {
+    foreach ($this->getHookListeners($hook) as $module => $listeners) {
+      if (isset($hook_listeners[$module])) {
+        $hook_listeners[$module] = array_merge($hook_listeners[$module], $listeners);
+      }
+      else {
+        $hook_listeners[$module] = $listeners;
+        // It is used by reference.
+        // @phpcs:ignore DrupalPractice.CodeAnalysis.VariableAnalysis.UnusedVariable
+        $extra_modules = TRUE;
+      }
+    }
+    return $hook_listeners;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php
index aeded22ca9268bd9dd896547130f85661d7ebad3..b6aa7fde5e7628aa57ae74a2b196e427c747d2c9 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php
@@ -217,7 +217,7 @@ public function hasImplementations(string $hook, $modules = NULL): bool;
    *
    * @param string $hook
    *   The name of the hook to invoke.
-   * @param callable $callback
+   * @param callable(callable, string): mixed $callback
    *   A callable that invokes a hook implementation. Such that
    *   $callback is callable(callable, string): mixed.
    *   Arguments:
diff --git a/core/lib/Drupal/Core/Extension/module.api.php b/core/lib/Drupal/Core/Extension/module.api.php
index 765992e0606d247e4556e3b54c5c362cddd2903f..d44b3d4f1c2ff6ef7280c0ce17e02e4cea82ae6f 100644
--- a/core/lib/Drupal/Core/Extension/module.api.php
+++ b/core/lib/Drupal/Core/Extension/module.api.php
@@ -94,6 +94,15 @@ function hook_hook_info(): array {
 /**
  * Alter the registry of modules implementing a hook.
  *
+ * This hook will be removed in 12.0.0.
+ * It has been intentionally not deprecated because custom code and contributed
+ * modules will still need to maintain implementations with the
+ * #[LegacyModuleImplementsAlter] attribute in order to support drupal versions
+ * older than 11.2.0.
+ *
+ * @link https://www.drupal.org/node/3496788
+ *
+ *
  * Only procedural implementations are supported for this hook.
  *
  * This hook is invoked in \Drupal::moduleHandler()->getImplementationInfo().
@@ -115,6 +124,8 @@ function hook_hook_info(): array {
  *   file named $module.$group.inc.
  * @param string $hook
  *   The name of the module hook being implemented.
+ *
+ * @see https://www.drupal.org/node/3496788
  */
 function hook_module_implements_alter(&$implementations, $hook) {
   if ($hook == 'form_alter') {
diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
index 1b220577a13130d6b8a790a5b1f41ecaebb60fd7..0a4c5b5cdec9c234917d0865bfd119a8fd7976ac 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -4,12 +4,17 @@
 
 namespace Drupal\Core\Hook\Attribute;
 
+use Drupal\Core\Hook\ComplexOrder;
+use Drupal\Core\Hook\HookOperation;
+use Drupal\Core\Hook\Order;
+
 /**
  * Attribute for defining a class method as a hook implementation.
  *
  * Hook implementations in classes need to be marked with this attribute,
  * using one of the following techniques:
  * - On a method, use this attribute with the hook name:
+ *
  *   @code
  *   #[Hook('user_cancel')]
  *   public function userCancel(...) {}
@@ -30,8 +35,14 @@
  *   }
  *   @endcode
  *
- * Ordering hook implementations can be done by implementing
- * hook_module_implements_alter.
+ * Ordering hook implementations can be done by using the order parameter.
+ *
+ * @see https://www.drupal.org/node/3493962
+ *
+ * Removing hook implementations can be done by using the attribute
+ * \Drupal\Core\Hook\Attribute/RemoveHook.
+ *
+ * @see https://www.drupal.org/node/3496786
  *
  * Classes that use this annotation on the class or on their methods are
  * automatically registered as autowired services with the class name as the
@@ -86,9 +97,11 @@
  * the procedural hook implementations.
  *
  * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
+ *
+ * @internal
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
-class Hook {
+class Hook extends HookOperation {
 
   /**
    * Constructs a Hook attribute object.
@@ -104,23 +117,38 @@ class Hook {
    *   (optional) The module this implementation is for. This allows one module
    *   to implement a hook on behalf of another module. Defaults to the module
    *   the implementation is in.
+   * @param \Drupal\Core\Hook\Order|\Drupal\Core\Hook\ComplexOrder|null $order
+   *   (optional) Set the order of the implementation.
    */
   public function __construct(
-    public string $hook,
-    public string $method = '',
+    string $hook,
+    string $method = '',
     public ?string $module = NULL,
-  ) {}
+    Order|ComplexOrder|null $order = NULL,
+  ) {
+    parent::__construct($hook, $method, order: $order);
+  }
 
   /**
-   * Set the method the hook should apply to.
+   * Set necessary parameters for the hook attribute.
    *
+   * @param class-string $class
+   *   The class for the hook.
+   * @param string $module
+   *   The module for the hook.
    * @param string $method
-   *   The method that the hook attribute applies to.
-   *   This only needs to be set when the attribute is on the class.
+   *   The method for the hook.
    */
-  public function setMethod(string $method): static {
-    $this->method = $method;
-    return $this;
+  public function set(string $class, string $module, string $method): void {
+    if (!$this->class) {
+      $this->class = $class;
+    }
+    if (!$this->module) {
+      $this->module = $module;
+    }
+    if (!$this->method) {
+      $this->method = $method;
+    }
   }
 
 }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/LegacyHook.php b/core/lib/Drupal/Core/Hook/Attribute/LegacyHook.php
index ee6501d7b42e7e57679002a651eb54dcfea32492..4b2726a635c2704cf6491b3a684273bedfe5dc5d 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/LegacyHook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/LegacyHook.php
@@ -19,6 +19,8 @@
  * only the legacy hook implementation is executed.
  *
  * For more information, see https://www.drupal.org/node/3442349.
+ *
+ * @internal
  */
 #[\Attribute(\Attribute::TARGET_FUNCTION)]
 class LegacyHook {
diff --git a/core/lib/Drupal/Core/Hook/Attribute/LegacyModuleImplementsAlter.php b/core/lib/Drupal/Core/Hook/Attribute/LegacyModuleImplementsAlter.php
new file mode 100644
index 0000000000000000000000000000000000000000..0940fdc012bd019570170eae7e43e6318cc538e0
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Attribute/LegacyModuleImplementsAlter.php
@@ -0,0 +1,26 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Attribute;
+
+/**
+ * Defines a LegacyModuleImplementsAlter object.
+ *
+ * This allows contrib and core to maintain legacy hook_module_implements_alter
+ * alongside the new attribute-based ordering. This means that a contrib module
+ * can simultaneously support Drupal 11.2 and older versions of Drupal.
+ *
+ * Marking hook_module_implements_alter as #LegacyModuleImplementsAlter will
+ * prevent hook_module_implements_alter from running when attribute-based
+ * ordering is available.
+ *
+ * On older versions of Drupal which are not aware of attribute-based ordering,
+ * only the legacy hook implementation is executed.
+ *
+ * For more information, see https://www.drupal.org/node/3496788.
+ *
+ * @internal
+ */
+#[\Attribute(\Attribute::TARGET_FUNCTION)]
+class LegacyModuleImplementsAlter {}
diff --git a/core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php b/core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php
new file mode 100644
index 0000000000000000000000000000000000000000..1f88da580f4bdbc7cfb0cec0e97e42e89fbcb579
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php
@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Attribute;
+
+use Drupal\Core\Hook\ComplexOrder;
+use Drupal\Core\Hook\HookOperation;
+use Drupal\Core\Hook\Order;
+
+/**
+ * Set the order of an already existing implementation.
+ *
+ * @internal
+ */
+#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class ReOrderHook extends HookOperation {
+
+  /**
+   * Constructs a ReOrderHook object.
+   *
+   * @param string $hook
+   *   The hook parameter of the #Hook being modified.
+   * @param class-string $class
+   *   The class the implementation to modify is in.
+   * @param string $method
+   *   The method name of the #Hook being modified. If the hook attribute is
+   *   on a class and does not have method set, then use __invoke.
+   * @param \Drupal\Core\Hook\Order|\Drupal\Core\Hook\ComplexOrder $order
+   *   Set the order of the implementation.
+   */
+  public function __construct(
+    string $hook,
+    string $class,
+    string $method,
+    Order|ComplexOrder $order,
+  ) {
+    parent::__construct($hook, $method, $class, $order);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php b/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
new file mode 100644
index 0000000000000000000000000000000000000000..5b4cca3b132a96bb8d2a8b9a681e94915518db19
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
@@ -0,0 +1,36 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Attribute;
+
+use Drupal\Core\Hook\HookOperation;
+
+/**
+ * Attribute for removing an implementation.
+ *
+ * @internal
+ */
+#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class RemoveHook extends HookOperation {
+
+  /**
+   * Constructs a RemoveHook object.
+   *
+   * @param string $hook
+   *   The hook parameter of the #Hook being modified.
+   * @param class-string $class
+   *   The class the implementation to modify is in.
+   * @param string $method
+   *   The method name of the #Hook being modified. If the hook attribute is
+   *   on a class and does not have method set, then use __invoke.
+   */
+  public function __construct(
+    string $hook,
+    string $class,
+    string $method,
+  ) {
+    parent::__construct($hook, $method, $class);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Hook/Attribute/StopProceduralHookScan.php b/core/lib/Drupal/Core/Hook/Attribute/StopProceduralHookScan.php
index 73f0ce6915bd37e3ff5476638893af4c910f7c2f..cc41fff51533a8c2a9f80ff1edfcccfdeef82ae2 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/StopProceduralHookScan.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/StopProceduralHookScan.php
@@ -9,6 +9,8 @@
  *
  * This allows contrib and core to mark when a file has no more
  * procedural hooks.
+ *
+ * @internal
  */
 #[\Attribute(\Attribute::TARGET_FUNCTION)]
 class StopProceduralHookScan {
diff --git a/core/lib/Drupal/Core/Hook/ComplexOrder.php b/core/lib/Drupal/Core/Hook/ComplexOrder.php
new file mode 100644
index 0000000000000000000000000000000000000000..a44e0bd2e815a2c560181ca1abf5efa28edef99f
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/ComplexOrder.php
@@ -0,0 +1,64 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook;
+
+/**
+ * Set this implementation to be before or after others.
+ */
+abstract readonly class ComplexOrder {
+
+  /**
+   * Whether the priority of this hook should be larger than others.
+   */
+  const bool VALUE = FALSE;
+
+  /**
+   * Whether the priority of this hook should be larger than others.
+   *
+   * This is fixed to the constant ::VALUE, it simplifies ordering by ensuring
+   * ComplexOrder and Order types both have a value property.
+   *
+   * @var bool
+   */
+  public bool $value;
+
+  /**
+   * Constructs a ComplexOrder object.
+   *
+   * @param list<string> $modules
+   *   A list of modules.
+   * @param list<array{class-string, string}> $classesAndMethods
+   *   A list of classes and methods, for example:
+   *   @code
+   *     [
+   *       [Foo::class, 'someMethod'],
+   *       [Bar::class, 'someOtherMethod'],
+   *     ]
+   *   @endcode
+   * @param list<string> $extraTypes
+   *   A list of hooks to be ordered together. Ordering by attributes happens
+   *   at build time by setting up the order of the listeners of a hook
+   *   correctly. However, ModuleHandlerInterface::alter() can be called with
+   *   multiple hooks runtime. If the hook defined on this method/class
+   *   requires ordering relative to other such hooks then this parameter can
+   *   be used to order relative to implementations of all hooks in the set.
+   *   Include all alter hooks to be ordered against in the set even if no
+   *   single alter() call includes all of them. For example, this can be used
+   *   to order a hook_form_BASE_FORM_ID_alter() implementation relative to
+   *   multiple hook_form_FORM_ID_alter() implementations as
+   *   Drupal\ckeditor5\Hook\Ckeditor5Hooks::formFilterFormatFormAlter() does.
+   */
+  public function __construct(
+    public array $modules = [],
+    public array $classesAndMethods = [],
+    public array $extraTypes = [],
+  ) {
+    if (!$this->modules && !$this->classesAndMethods) {
+      throw new \LogicException('Order must provide either modules or class-method pairs to order against.');
+    }
+    $this->value = static::VALUE;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Hook/HookCollector.php b/core/lib/Drupal/Core/Hook/HookCollector.php
new file mode 100644
index 0000000000000000000000000000000000000000..2008b0858287f7d5c6f4d0bc5702bd0ef7c098ec
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/HookCollector.php
@@ -0,0 +1,612 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook;
+
+use Drupal\Component\Annotation\Doctrine\StaticReflectionParser;
+use Drupal\Component\Annotation\Reflection\MockFileFinder;
+use Drupal\Component\FileCache\FileCacheFactory;
+use Drupal\Core\Extension\ProceduralCall;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\Core\Hook\Attribute\LegacyModuleImplementsAlter;
+use Drupal\Core\Hook\Attribute\RemoveHook;
+use Drupal\Core\Hook\Attribute\ReOrderHook;
+use Drupal\Core\Hook\Attribute\StopProceduralHookScan;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+/**
+ * Collects and registers hook implementations.
+ *
+ * A hook implementation is a class in a Drupal\modulename\Hook namespace
+ * where either the class itself or the methods have a #[Hook] attribute.
+ * These classes are automatically registered as autowired services.
+ *
+ * Services for procedural implementation of hooks are also registered
+ * using the ProceduralCall class.
+ *
+ * Finally, a hook_implementations_map container parameter is added. This
+ * contains a mapping from [hook,class,method] to the module name.
+ */
+class HookCollector {
+
+  /**
+   * A map of include files by function name.
+   *
+   * (This is required only for BC.)
+   *
+   * @var array<string, string>
+   */
+  protected array $includes = [];
+
+  /**
+   * A list of functions implementing hook_module_implements_alter().
+   *
+   * (This is required only for BC.)
+   *
+   * @var list<callable-string>
+   */
+  protected array $moduleImplementsAlters = [];
+
+  /**
+   * A list of functions implementing hook_hook_info().
+   *
+   * (This is required only for BC.)
+   *
+   * @var list<callable-string>
+   */
+  private array $hookInfo = [];
+
+  /**
+   * Include files, keyed by the $group part of "/$module.$group.inc".
+   *
+   * @var array<string, list<string>>
+   */
+  private array $groupIncludes = [];
+
+  /**
+   * Implementations, as module names keyed by hook name and "$class::$method".
+   *
+   * @var array<string, array<string, string>>
+   */
+  protected array $implementations = [];
+
+  /**
+   * @var array<int, list<\Drupal\Core\Hook\HookOperation>>
+   */
+  protected array $orderAttributesByPhase = [0 => [], 1 => []];
+
+  /**
+   * @var list<\Drupal\Core\Hook\Attribute\RemoveHook>
+   */
+  protected array $removeHookAttributes = [];
+
+  /**
+   * Constructor. Should not be called directly.
+   *
+   * @param list<string> $modules
+   *   Names of installed modules.
+   */
+  protected function __construct(
+    protected readonly array $modules,
+  ) {}
+
+  /**
+   * Writes collected definitions to the container builder.
+   *
+   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
+   *   Container builder.
+   */
+  public function writeToContainer(ContainerBuilder $container): void {
+    $orderExtraTypes = $this->getOrderExtraTypes();
+
+    $container->register(ProceduralCall::class, ProceduralCall::class)
+      ->addArgument($this->includes);
+
+    // Gather includes for each hook_hook_info group.
+    // We store this in $groupIncludes so moduleHandler can ensure the files
+    // are included runtime when the hooks are invoked.
+    $groupIncludes = [];
+    foreach ($this->hookInfo as $function) {
+      foreach ($function() as $hook => $info) {
+        if (isset($this->groupIncludes[$info['group']])) {
+          $groupIncludes[$hook] = $this->groupIncludes[$info['group']];
+        }
+      }
+    }
+
+    $implementationsByHook = $this->calculateImplementations($orderExtraTypes);
+
+    static::writeImplementationsToContainer($container, $implementationsByHook);
+
+    // Update the module handler definition.
+    $definition = $container->getDefinition('module_handler');
+    $definition->setArgument('$groupIncludes', $groupIncludes);
+    $definition->setArgument('$orderedExtraTypes', $orderExtraTypes);
+  }
+
+  /**
+   * Gets implementation lists with removals already applied.
+   *
+   * @return array<string, list<string>>
+   *   Implementations, as module names keyed by hook name and
+   *   "$class::$method".
+   */
+  protected function getFilteredImplementations(): array {
+    $implementationsByHook = $this->implementations;
+    foreach ($this->removeHookAttributes as $removeHook) {
+      unset($implementationsByHook[$removeHook->hook][$removeHook->class . '::' . $removeHook->method]);
+    }
+    return $implementationsByHook;
+  }
+
+  /**
+   * Gets groups of extra hooks from collected data.
+   *
+   * @return array<string, list<string>>
+   *   Lists of extra hooks keyed by main hook.
+   */
+  protected function getOrderExtraTypes(): array {
+    // Loop over all ReOrderHook attributes and gather order information
+    // before registering the hooks. This must happen after all collection,
+    // but before registration to ensure this ordering directive takes
+    // precedence.
+    /** @var list<\Drupal\Core\Hook\HookOperation> $hookOrderOperations */
+    $hookOrderOperations = array_merge(...$this->orderAttributesByPhase);
+    $orderExtraTypes = [];
+    foreach ($hookOrderOperations as $hookWithOrder) {
+      if ($hookWithOrder->order instanceof ComplexOrder && $hookWithOrder->order->extraTypes) {
+        $extraTypes = [... $hookWithOrder->order->extraTypes, $hookWithOrder->hook];
+        foreach ($extraTypes as $extraHook) {
+          $orderExtraTypes[$extraHook] = array_merge($orderExtraTypes[$extraHook] ?? [], $extraTypes);
+        }
+      }
+    }
+    $orderExtraTypes = array_map('array_unique', $orderExtraTypes);
+    return array_map('array_values', $orderExtraTypes);
+  }
+
+  /**
+   * Calculates the ordered implementations.
+   *
+   * @param array<string, list<string>> $orderExtraTypes
+   *   Extra types to order a hook with.
+   *
+   * @return array<string, array<string, string>>
+   *   Implementations, as module names keyed by hook name and "$class::$method"
+   *   identifier.
+   */
+  protected function calculateImplementations(array $orderExtraTypes): array {
+    $implementationsByHookOrig = $this->getFilteredImplementations();
+
+    // List of hooks and modules formatted for hook_module_implements_alter().
+    $moduleImplementsMap = [];
+    foreach ($implementationsByHookOrig as $hook => $hookImplementations) {
+      foreach ($hookImplementations as $module) {
+        $moduleImplementsMap[$hook][$module] = '';
+      }
+    }
+
+    $implementationsByHook = [];
+    foreach ($moduleImplementsMap as $hook => $moduleImplements) {
+      $extraHooks = $orderExtraTypes[$hook] ?? [];
+      // Add implementations to the array we pass to legacy ordering
+      // when the definition specifies that they should be ordered together.
+      foreach ($extraHooks as $extraHook) {
+        $moduleImplements += $moduleImplementsMap[$extraHook] ?? [];
+      }
+      // Process all hook_module_implements_alter() for build time ordering.
+      foreach ($this->moduleImplementsAlters as $alter) {
+        $alter($moduleImplements, $hook);
+      }
+      foreach ($moduleImplements as $module => $v) {
+        foreach (array_keys($implementationsByHookOrig[$hook], $module, TRUE) as $identifier) {
+          $implementationsByHook[$hook][$identifier] = $module;
+        }
+        if (count($extraHooks) > 1) {
+          $combinedHook = implode(':', $extraHooks);
+          foreach ($extraHooks as $extraHook) {
+            foreach (array_keys($implementationsByHookOrig[$extraHook] ?? [], $module, TRUE) as $identifier) {
+              $implementationsByHook[$combinedHook][$identifier] = $module;
+            }
+          }
+        }
+      }
+    }
+
+    /** @var list<\Drupal\Core\Hook\HookOperation> $hookOrderOperations */
+    $hookOrderOperations = array_merge(...$this->orderAttributesByPhase);
+    foreach ($hookOrderOperations as $hookOrderOperation) {
+      static::applyOrderAttributeOperation(
+        $implementationsByHook,
+        $orderExtraTypes,
+        $hookOrderOperation,
+      );
+    }
+
+    return $implementationsByHook;
+  }
+
+  /**
+   * Applies hook order changes from a single attribute with order information.
+   *
+   * @param array<string, array<string, string>> $implementationsByHook
+   *   Implementations, as module names keyed by hook name and "$class::$method"
+   *   identifier.
+   * @param array<string, list<string>> $orderExtraTypes
+   *   Extra types to order a hook with.
+   * @param \Drupal\Core\Hook\HookOperation $hookOrderOperation
+   *   Hook attribute with order information.
+   */
+  protected static function applyOrderAttributeOperation(
+    array &$implementationsByHook,
+    array $orderExtraTypes,
+    HookOperation $hookOrderOperation,
+  ): void {
+    // ::process() adds the hook serving as key to the order extraTypes so it
+    // does not need to be added if there's a extraTypes for the hook.
+    $hooks = $orderExtraTypes[$hookOrderOperation->hook] ?? [$hookOrderOperation->hook];
+    $combinedHook = implode(':', $hooks);
+    $identifier = $hookOrderOperation->class . '::' . $hookOrderOperation->method;
+    $module = $implementationsByHook[$combinedHook][$identifier] ?? NULL;
+    if ($module === NULL) {
+      // Implementation is not in the list. Nothing to reorder.
+      return;
+    }
+    $list = $implementationsByHook[$combinedHook];
+    $order = $hookOrderOperation->order;
+    if ($order === NULL) {
+      throw new \InvalidArgumentException('This method must only be called with attributes that have order information.');
+    }
+    if ($order === Order::First) {
+      unset($list[$identifier]);
+      $list = [$identifier => $module] + $list;
+    }
+    elseif ($order === Order::Last) {
+      unset($list[$identifier]);
+      $list[$identifier] = $module;
+    }
+    elseif ($order instanceof ComplexOrder) {
+      $shouldBeAfter = !$order->value;
+      unset($list[$identifier]);
+      $identifiers = array_keys($list);
+      $modules = array_values($list);
+      $compareIndices = [];
+      if (isset($hookOrderOperation->order->modules)) {
+        $compareIndices = array_keys(array_intersect($modules, $hookOrderOperation->order->modules));
+      }
+      foreach ($hookOrderOperation->order->classesAndMethods as [$otherClass, $otherMethod]) {
+        $compareIndices[] = array_search("$otherClass::$otherMethod", $identifiers, TRUE);
+      }
+      if (!$compareIndices) {
+        return;
+      }
+      $splice_index = $shouldBeAfter
+        ? max($compareIndices) + 1
+        : min($compareIndices);
+      array_splice($identifiers, $splice_index, 0, [$identifier]);
+      array_splice($modules, $splice_index, 0, [$module]);
+      $list = array_combine($identifiers, $modules);
+    }
+    $implementationsByHook[$combinedHook] = $list;
+  }
+
+  /**
+   * Writes all implementations to the container.
+   *
+   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
+   *   The container builder.
+   * @param array<string, array<string, string>> $implementationsByHook
+   *   Implementations, as module names keyed by hook name and "$class::$method"
+   *   identifier.
+   */
+  protected static function writeImplementationsToContainer(
+    ContainerBuilder $container,
+    array $implementationsByHook,
+  ): void {
+    $map = [];
+    $tagsInfoByClass = [];
+    foreach ($implementationsByHook as $hook => $hookImplementations) {
+      $priority = 0;
+      foreach ($hookImplementations as $class_and_method => $module) {
+        [$class, $method] = explode('::', $class_and_method);
+        $tagsInfoByClass[$class][] = [
+          'event' => "drupal_hook.$hook",
+          'method' => $method,
+          'priority' => $priority,
+        ];
+        --$priority;
+        $map[$hook][$class][$method] = $module;
+      }
+    }
+
+    foreach ($tagsInfoByClass as $class => $tagsInfo) {
+      if ($container->hasDefinition($class)) {
+        $definition = $container->findDefinition($class);
+      }
+      else {
+        $definition = $container
+          ->register($class, $class)
+          ->setAutowired(TRUE);
+      }
+      foreach ($tagsInfo as $tag_info) {
+        $definition->addTag('kernel.event_listener', $tag_info);
+      }
+    }
+
+    $container->setParameter('hook_implementations_map', $map);
+  }
+
+  /**
+   * Collects all hook implementations.
+   *
+   * @param array<string, array{pathname: string}> $module_list
+   *   An associative array. Keys are the module names, values are relevant
+   *   info yml file path.
+   * @param list<string> $skipProceduralModules
+   *   Module names that are known to not have procedural hook implementations.
+   *
+   * @return static
+   *   A HookCollectorPass instance holding all hook implementations and
+   *   include file information.
+   *
+   * @internal
+   *   This method is only used by ModuleHandler.
+   *
+   * @todo Pass only $container when ModuleHandler::add() is removed
+   * @see https://www.drupal.org/project/drupal/issues/3481778
+   */
+  public static function collectAllHookImplementations(array $module_list, array $skipProceduralModules = []): static {
+    $modules = array_keys($module_list);
+    // Longer modules first.
+    usort($modules, fn ($a, $b) => strlen($b) - strlen($a));
+    $known_modules_pattern = implode('|', array_map(
+      static fn ($x) => preg_quote($x, '/'),
+      $modules,
+    ));
+    $module_preg = '/^(?<function>(?<module>' . $known_modules_pattern . ')_(?!preprocess_)(?!update_\d)(?<hook>[a-zA-Z0-9_\x80-\xff]+$))/';
+    $collector = new static($modules);
+    foreach ($module_list as $module => $info) {
+      $skip_procedural = in_array($module, $skipProceduralModules);
+      $collector->collectModuleHookImplementations(dirname($info['pathname']), $module, $module_preg, $skip_procedural);
+    }
+    return $collector;
+  }
+
+  /**
+   * Collects procedural and Attribute hook implementations.
+   *
+   * @param string $dir
+   *   The directory in which the module resides.
+   * @param string $module
+   *   The name of the module.
+   * @param string $module_preg
+   *   A regular expression matching every module, longer module names are
+   *   matched first.
+   * @param bool $skip_procedural
+   *   Skip the procedural check for the current module.
+   */
+  protected function collectModuleHookImplementations($dir, $module, $module_preg, bool $skip_procedural): void {
+    $hook_file_cache = FileCacheFactory::get('hook_implementations');
+    $procedural_hook_file_cache = FileCacheFactory::get('procedural_hook_implementations:' . $module_preg);
+
+    $iterator = new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::FOLLOW_SYMLINKS);
+    $iterator = new \RecursiveCallbackFilterIterator($iterator, static::filterIterator(...));
+    $iterator = new \RecursiveIteratorIterator($iterator);
+    /** @var \RecursiveDirectoryIterator | \RecursiveIteratorIterator $iterator*/
+    foreach ($iterator as $fileinfo) {
+      assert($fileinfo instanceof \SplFileInfo);
+      $extension = $fileinfo->getExtension();
+      $filename = $fileinfo->getPathname();
+
+      if (($extension === 'module' || $extension === 'profile') && !$iterator->getDepth() && !$skip_procedural) {
+        // There is an expectation for all modules and profiles to be loaded.
+        // .module and .profile files are not supposed to be in subdirectories.
+        // These need to be loaded even if the module has no procedural hooks.
+        include_once $filename;
+      }
+      if ($extension === 'php') {
+        $cached = $hook_file_cache->get($filename);
+        if ($cached) {
+          $class = $cached['class'];
+          $attributes = $cached['attributes'];
+        }
+        else {
+          $namespace = preg_replace('#^src/#', "Drupal/$module/", $iterator->getSubPath());
+          $class = $namespace . '/' . $fileinfo->getBasename('.php');
+          $class = str_replace('/', '\\', $class);
+          $attributes = [];
+          if (class_exists($class)) {
+            $reflectionClass = new \ReflectionClass($class);
+            $attributes = self::getAttributeInstances($reflectionClass);
+            $hook_file_cache->set($filename, ['class' => $class, 'attributes' => $attributes]);
+          }
+        }
+        foreach ($attributes as $method => $methodAttributes) {
+          foreach ($methodAttributes as $attribute) {
+            if ($attribute instanceof Hook) {
+              self::checkForProceduralOnlyHooks($attribute, $class);
+              $this->implementations[$attribute->hook][$class . '::' . ($attribute->method ?: $method)] = $attribute->module ?? $module;
+              if ($attribute->order !== NULL) {
+                $attribute->set($class, $attribute->module ?? $module, $method);
+                $this->orderAttributesByPhase[0][] = $attribute;
+              }
+            }
+            elseif ($attribute instanceof ReOrderHook) {
+              $this->orderAttributesByPhase[1][] = $attribute;
+            }
+            elseif ($attribute instanceof RemoveHook) {
+              $this->removeHookAttributes[] = $attribute;
+            }
+          }
+        }
+      }
+      elseif (!$skip_procedural) {
+        $implementations = $procedural_hook_file_cache->get($filename);
+        if ($implementations === NULL) {
+          $finder = MockFileFinder::create($filename);
+          $parser = new StaticReflectionParser('', $finder);
+          $implementations = [];
+          foreach ($parser->getMethodAttributes() as $function => $attributes) {
+            if (StaticReflectionParser::hasAttribute($attributes, StopProceduralHookScan::class)) {
+              break;
+            }
+            if (!StaticReflectionParser::hasAttribute($attributes, LegacyHook::class) && preg_match($module_preg, $function, $matches) && !StaticReflectionParser::hasAttribute($attributes, LegacyModuleImplementsAlter::class)) {
+              $implementations[] = ['function' => $function, 'module' => $matches['module'], 'hook' => $matches['hook']];
+            }
+          }
+          $procedural_hook_file_cache->set($filename, $implementations);
+        }
+        foreach ($implementations as $implementation) {
+          $this->addProceduralImplementation($fileinfo, $implementation['hook'], $implementation['module'], $implementation['function']);
+        }
+      }
+      if ($extension === 'inc') {
+        $parts = explode('.', $fileinfo->getFilename());
+        if (count($parts) === 3 && $parts[0] === $module) {
+          $this->groupIncludes[$parts[1]][] = $filename;
+        }
+      }
+    }
+  }
+
+  /**
+   * Filter iterator callback. Allows include files and .php files in src/Hook.
+   */
+  protected static function filterIterator(\SplFileInfo $fileInfo, $key, \RecursiveDirectoryIterator $iterator): bool {
+    $sub_path_name = $iterator->getSubPathname();
+    $extension = $fileInfo->getExtension();
+    if (str_starts_with($sub_path_name, 'src/Hook/')) {
+      return $iterator->isDir() || $extension === 'php';
+    }
+    if ($iterator->isDir()) {
+      if ($sub_path_name === 'src' || $sub_path_name === 'src/Hook') {
+        return TRUE;
+      }
+      // glob() doesn't support streams but scandir() does.
+      return !in_array($fileInfo->getFilename(), ['tests', 'js', 'css']) && !array_filter(scandir($key), fn ($filename) => str_ends_with($filename, '.info.yml'));
+    }
+    return in_array($extension, ['inc', 'module', 'profile', 'install']);
+  }
+
+  /**
+   * Adds a procedural hook implementation.
+   *
+   * @param \SplFileInfo $fileinfo
+   *   The file this procedural implementation is in.
+   * @param string $hook
+   *   The name of the hook.
+   * @param string $module
+   *   The module of the hook. Note this might be different from the module the
+   *   function is in.
+   * @param string $function
+   *   The name of function implementing the hook.
+   */
+  protected function addProceduralImplementation(\SplFileInfo $fileinfo, string $hook, string $module, string $function): void {
+    if ($hook === 'hook_info') {
+      $this->hookInfo[] = $function;
+    }
+    elseif ($hook === 'module_implements_alter') {
+      $message = "$function without a #[LegacyModuleImplementsAlter] attribute is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. See https://www.drupal.org/node/3496788";
+      @trigger_error($message, E_USER_DEPRECATED);
+      $this->moduleImplementsAlters[] = $function;
+    }
+    $this->implementations[$hook][ProceduralCall::class . '::' . $module . '_' . $hook] = $module;
+    if ($fileinfo->getExtension() !== 'module') {
+      $this->includes[$function] = $fileinfo->getPathname();
+    }
+  }
+
+  /**
+   * This method is only to be used by ModuleHandler.
+   *
+   * @todo remove when ModuleHandler::add() is removed.
+   * @see https://www.drupal.org/project/drupal/issues/3481778
+   *
+   * @internal
+   */
+  public function loadAllIncludes(): void {
+    foreach ($this->includes as $include) {
+      include_once $include;
+    }
+  }
+
+  /**
+   * This method is only to be used by ModuleHandler.
+   *
+   * @return array<string, array<string, array<class-string, array<string, string>>>>
+   *   Hook implementation method names keyed by hook, module, class and method.
+   *
+   * @todo remove when ModuleHandler::add() is removed.
+   *   See https://www.drupal.org/project/drupal/issues/3481778
+   *
+   * @internal
+   */
+  public function getImplementations(): array {
+    $implementationsByHook = $this->getFilteredImplementations();
+
+    // List of modules implementing hooks with the implementation details.
+    $implementations = [];
+
+    foreach ($implementationsByHook as $hook => $hookImplementations) {
+      foreach ($this->modules as $module) {
+        foreach (array_keys($hookImplementations, $module, TRUE) as $identifier) {
+          [$class, $method] = explode('::', $identifier);
+          $implementations[$hook][$module][$class][$method] = $method;
+        }
+      }
+    }
+
+    return $implementations;
+  }
+
+  /**
+   * Checks for hooks which can't be supported in classes.
+   *
+   * @param \Drupal\Core\Hook\Attribute\Hook $hookAttribute
+   *   The hook to check.
+   * @param class-string $class
+   *   The class the hook is implemented on.
+   */
+  public static function checkForProceduralOnlyHooks(Hook $hookAttribute, string $class): void {
+    $staticDenyHooks = [
+      'hook_info',
+      'install',
+      'module_implements_alter',
+      'requirements',
+      'schema',
+      'uninstall',
+      'update_last_removed',
+      'install_tasks',
+      'install_tasks_alter',
+    ];
+
+    if (in_array($hookAttribute->hook, $staticDenyHooks) || preg_match('/^(post_update_|preprocess_|update_\d+$)/', $hookAttribute->hook)) {
+      throw new \LogicException("The hook $hookAttribute->hook on class $class does not support attributes and must remain procedural.");
+    }
+  }
+
+  /**
+   * Get attribute instances from class and method reflections.
+   *
+   * @param \ReflectionClass $reflectionClass
+   *   A reflected class.
+   *
+   * @return array<string, list<\Drupal\Core\Hook\HookOperation>>
+   *   Lists of Hook attribute instances by method name.
+   */
+  protected static function getAttributeInstances(\ReflectionClass $reflectionClass): array {
+    $attributes = [];
+    $reflections = $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC);
+    $reflections[] = $reflectionClass;
+    foreach ($reflections as $reflection) {
+      if ($reflectionAttributes = $reflection->getAttributes(HookOperation::class, \ReflectionAttribute::IS_INSTANCEOF)) {
+        $method = $reflection instanceof \ReflectionMethod ? $reflection->getName() : '__invoke';
+        $attributes[$method] = array_map(static fn (\ReflectionAttribute $ra) => $ra->newInstance(), $reflectionAttributes);
+      }
+    }
+    return $attributes;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 3809e24af21d56b20f034b2a45848de6d47e6e93..2839dc110919f3f5a8332f1466df983e48eae87d 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -4,13 +4,6 @@
 
 namespace Drupal\Core\Hook;
 
-use Drupal\Component\Annotation\Doctrine\StaticReflectionParser;
-use Drupal\Component\Annotation\Reflection\MockFileFinder;
-use Drupal\Component\FileCache\FileCacheFactory;
-use Drupal\Core\Extension\ProceduralCall;
-use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\LegacyHook;
-use Drupal\Core\Hook\Attribute\StopProceduralHookScan;
 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 
@@ -29,359 +22,19 @@
  */
 class HookCollectorPass implements CompilerPassInterface {
 
-  /**
-   * An associative array of hook implementations.
-   *
-   * Keys are hook, module, class. Values are a list of methods.
-   */
-  protected array $implementations = [];
-
-  /**
-   * An associative array of hook implementations.
-   *
-   * Keys are hook, module and an empty string value.
-   *
-   * @see hook_module_implements_alter()
-   */
-  protected array $moduleImplements = [];
-
-  /**
-   * A list of include files.
-   *
-   * (This is required only for BC.)
-   */
-  protected array $includes = [];
-
-  /**
-   * A list of functions implementing hook_module_implements_alter().
-   *
-   * (This is required only for BC.)
-   */
-  protected array $moduleImplementsAlters = [];
-
-  /**
-   * A list of functions implementing hook_hook_info().
-   *
-   * (This is required only for BC.)
-   */
-  private array $hookInfo = [];
-
-  /**
-   * A list of .inc files.
-   */
-  private array $groupIncludes = [];
-
   /**
    * {@inheritdoc}
    */
   public function process(ContainerBuilder $container): void {
-    $collector = static::collectAllHookImplementations($container->getParameter('container.modules'), $container);
-    $map = [];
-    $container->register(ProceduralCall::class, ProceduralCall::class)
-      ->addArgument($collector->includes);
-    $groupIncludes = [];
-    foreach ($collector->hookInfo as $function) {
-      foreach ($function() as $hook => $info) {
-        if (isset($collector->groupIncludes[$info['group']])) {
-          $groupIncludes[$hook] = $collector->groupIncludes[$info['group']];
-        }
-      }
-    }
-    $definition = $container->getDefinition('module_handler');
-    $definition->setArgument('$groupIncludes', $groupIncludes);
-    foreach ($collector->moduleImplements as $hook => $moduleImplements) {
-      foreach ($collector->moduleImplementsAlters as $alter) {
-        $alter($moduleImplements, $hook);
-      }
-      $priority = 0;
-      foreach ($moduleImplements as $module => $v) {
-        foreach ($collector->implementations[$hook][$module] as $class => $method_hooks) {
-          if ($container->has($class)) {
-            $definition = $container->findDefinition($class);
-          }
-          else {
-            $definition = $container
-              ->register($class, $class)
-              ->setAutowired(TRUE);
-          }
-          foreach ($method_hooks as $method) {
-            $map[$hook][$class][$method] = $module;
-            $definition->addTag('kernel.event_listener', [
-              'event' => "drupal_hook.$hook",
-              'method' => $method,
-              'priority' => $priority--,
-            ]);
-          }
-        }
-      }
-    }
-    $container->setParameter('hook_implementations_map', $map);
-  }
-
-  /**
-   * Collects all hook implementations.
-   *
-   * @param array $module_filenames
-   *   An associative array. Keys are the module names, values are relevant
-   *   info yml file path.
-   * @param Symfony\Component\DependencyInjection\ContainerBuilder|null $container
-   *   The container.
-   *
-   * @return static
-   *   A HookCollectorPass instance holding all hook implementations and
-   *   include file information.
-   *
-   * @internal
-   *   This method is only used by ModuleHandler.
-   *
-   * @todo Pass only $container when ModuleHandler->add is removed
-   *   https://www.drupal.org/project/drupal/issues/3481778
-   */
-  public static function collectAllHookImplementations(array $module_filenames, ?ContainerBuilder $container = NULL): static {
-    $modules = array_map(fn ($x) => preg_quote($x, '/'), array_keys($module_filenames));
-    // Longer modules first.
-    usort($modules, fn($a, $b) => strlen($b) - strlen($a));
-    $module_preg = '/^(?<function>(?<module>' . implode('|', $modules) . ')_(?!preprocess_)(?!update_\d)(?<hook>[a-zA-Z0-9_\x80-\xff]+$))/';
-    $collector = new static();
-    foreach ($module_filenames as $module => $info) {
-      $skip_procedural = FALSE;
-      if ($container?->hasParameter("$module.hooks_converted")) {
-        $skip_procedural = $container->getParameter("$module.hooks_converted");
-      }
-      $collector->collectModuleHookImplementations(dirname($info['pathname']), $module, $module_preg, $skip_procedural);
-    }
-    return $collector;
-  }
-
-  /**
-   * Collects procedural and Attribute hook implementations.
-   *
-   * @param string $dir
-   *   The directory in which the module resides.
-   * @param string $module
-   *   The name of the module.
-   * @param string $module_preg
-   *   A regular expression matching every module, longer module names are
-   *   matched first.
-   * @param bool $skip_procedural
-   *   Skip the procedural check for the current module.
-   */
-  protected function collectModuleHookImplementations($dir, $module, $module_preg, bool $skip_procedural): void {
-    $hook_file_cache = FileCacheFactory::get('hook_implementations');
-    $procedural_hook_file_cache = FileCacheFactory::get('procedural_hook_implementations:' . $module_preg);
-
-    $iterator = new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::FOLLOW_SYMLINKS);
-    $iterator = new \RecursiveCallbackFilterIterator($iterator, static::filterIterator(...));
-    $iterator = new \RecursiveIteratorIterator($iterator);
-    /** @var \RecursiveDirectoryIterator | \RecursiveIteratorIterator $iterator*/
-    foreach ($iterator as $fileinfo) {
-      assert($fileinfo instanceof \SplFileInfo);
-      $extension = $fileinfo->getExtension();
-      $filename = $fileinfo->getPathname();
-
-      if (($extension === 'module' || $extension === 'profile') && !$iterator->getDepth() && !$skip_procedural) {
-        // There is an expectation for all modules and profiles to be loaded.
-        // .module and .profile files are not supposed to be in subdirectories.
-        // These need to be loaded even if the module has no procedural hooks.
-        include_once $filename;
-      }
-      if ($extension === 'php') {
-        $cached = $hook_file_cache->get($filename);
-        if ($cached) {
-          $class = $cached['class'];
-          $attributes = $cached['attributes'];
-        }
-        else {
-          $namespace = preg_replace('#^src/#', "Drupal/$module/", $iterator->getSubPath());
-          $class = $namespace . '/' . $fileinfo->getBasename('.php');
-          $class = str_replace('/', '\\', $class);
-          if (class_exists($class)) {
-            $attributes = static::getHookAttributesInClass($class);
-            $hook_file_cache->set($filename, ['class' => $class, 'attributes' => $attributes]);
-          }
-          else {
-            $attributes = [];
-          }
-        }
-        foreach ($attributes as $attribute) {
-          $this->addFromAttribute($attribute, $class, $module);
-        }
-      }
-      elseif (!$skip_procedural) {
-        $implementations = $procedural_hook_file_cache->get($filename);
-        if ($implementations === NULL) {
-          $finder = MockFileFinder::create($filename);
-          $parser = new StaticReflectionParser('', $finder);
-          $implementations = [];
-          foreach ($parser->getMethodAttributes() as $function => $attributes) {
-            if (StaticReflectionParser::hasAttribute($attributes, StopProceduralHookScan::class)) {
-              break;
-            }
-            if (!StaticReflectionParser::hasAttribute($attributes, LegacyHook::class) && preg_match($module_preg, $function, $matches)) {
-              $implementations[] = ['function' => $function, 'module' => $matches['module'], 'hook' => $matches['hook']];
-            }
-          }
-          $procedural_hook_file_cache->set($filename, $implementations);
-        }
-        foreach ($implementations as $implementation) {
-          $this->addProceduralImplementation($fileinfo, $implementation['hook'], $implementation['module'], $implementation['function']);
-        }
-      }
-      if ($extension === 'inc') {
-        $parts = explode('.', $fileinfo->getFilename());
-        if (count($parts) === 3 && $parts[0] === $module) {
-          $this->groupIncludes[$parts[1]][] = $filename;
-        }
-      }
-    }
-  }
-
-  /**
-   * Filter iterator callback. Allows include files and .php files in src/Hook.
-   */
-  protected static function filterIterator(\SplFileInfo $fileInfo, $key, \RecursiveDirectoryIterator $iterator): bool {
-    $sub_path_name = $iterator->getSubPathname();
-    $extension = $fileInfo->getExtension();
-    if (str_starts_with($sub_path_name, 'src/Hook/')) {
-      return $iterator->isDir() || $extension === 'php';
-    }
-    if ($iterator->isDir()) {
-      if ($sub_path_name === 'src' || $sub_path_name === 'src/Hook') {
-        return TRUE;
-      }
-      // glob() doesn't support streams but scandir() does.
-      return !in_array($fileInfo->getFilename(), ['tests', 'js', 'css']) && !array_filter(scandir($key), fn ($filename) => str_ends_with($filename, '.info.yml'));
-    }
-    return in_array($extension, ['inc', 'module', 'profile', 'install']);
-  }
-
-  /**
-   * An array of Hook attributes on this class with $method set.
-   *
-   * @param string $class
-   *   The class.
-   *
-   * @return \Drupal\Core\Hook\Attribute\Hook[]
-   *   An array of Hook attributes on this class. The $method property is
-   *   guaranteed to be set.
-   */
-  protected static function getHookAttributesInClass(string $class): array {
-    $reflection_class = new \ReflectionClass($class);
-    $class_implementations = [];
-    // Check for #[Hook] on the class itself.
-    foreach ($reflection_class->getAttributes(Hook::class, \ReflectionAttribute::IS_INSTANCEOF) as $reflection_attribute) {
-      $hook = $reflection_attribute->newInstance();
-      assert($hook instanceof Hook);
-      self::checkForProceduralOnlyHooks($hook, $class);
-      if (!$hook->method) {
-        if (method_exists($class, '__invoke')) {
-          $hook->setMethod('__invoke');
-        }
-        else {
-          throw new \LogicException("The Hook attribute for hook $hook->hook on class $class must specify a method.");
-        }
-      }
-      $class_implementations[] = $hook;
-    }
-    // Check for #[Hook] on methods.
-    foreach ($reflection_class->getMethods(\ReflectionMethod::IS_PUBLIC) as $method_reflection) {
-      foreach ($method_reflection->getAttributes(Hook::class, \ReflectionAttribute::IS_INSTANCEOF) as $attribute_reflection) {
-        $hook = $attribute_reflection->newInstance();
-        assert($hook instanceof Hook);
-        self::checkForProceduralOnlyHooks($hook, $class);
-        $class_implementations[] = $hook->setMethod($method_reflection->getName());
-      }
-    }
-    return $class_implementations;
-  }
-
-  /**
-   * Adds a Hook attribute implementation.
-   *
-   * @param \Drupal\Core\Hook\Attribute\Hook $hook
-   *   A hook attribute.
-   * @param string $class
-   *   The class in which said attribute resides in.
-   * @param string $module
-   *   The module in which the class resides in.
-   */
-  protected function addFromAttribute(Hook $hook, $class, $module): void {
-    if ($hook->module) {
-      $module = $hook->module;
-    }
-    $this->moduleImplements[$hook->hook][$module] = '';
-    $this->implementations[$hook->hook][$module][$class][] = $hook->method;
-  }
-
-  /**
-   * Adds a procedural hook implementation.
-   *
-   * @param \SplFileInfo $fileinfo
-   *   The file this procedural implementation is in. (You don't say)
-   * @param string $hook
-   *   The name of the hook. (Huh, right?)
-   * @param string $module
-   *   The name of the module. (Truly shocking!)
-   * @param string $function
-   *   The name of function implementing the hook. (Wow!)
-   */
-  protected function addProceduralImplementation(\SplFileInfo $fileinfo, string $hook, string $module, string $function): void {
-    $this->addFromAttribute(new Hook($hook, $module . '_' . $hook), ProceduralCall::class, $module);
-    if ($hook === 'hook_info') {
-      $this->hookInfo[] = $function;
-    }
-    if ($hook === 'module_implements_alter') {
-      $this->moduleImplementsAlters[] = $function;
-    }
-    if ($fileinfo->getExtension() !== 'module') {
-      $this->includes[$function] = $fileinfo->getPathname();
-    }
-  }
-
-  /**
-   * This method is only to be used by ModuleHandler.
-   *
-   * @internal
-   */
-  public function loadAllIncludes(): void {
-    foreach ($this->includes as $include) {
-      include_once $include;
-    }
-  }
-
-  /**
-   * This method is only to be used by ModuleHandler.
-   *
-   * @internal
-   */
-  public function getImplementations(): array {
-    return $this->implementations;
-  }
-
-  /**
-   * Checks for hooks which can't be supported in classes.
-   *
-   * @param \Drupal\Core\Hook\Attribute\Hook $hook
-   *   The hook to check.
-   * @param string $class
-   *   The class the hook is implemented on.
-   */
-  public static function checkForProceduralOnlyHooks(Hook $hook, string $class): void {
-    $staticDenyHooks = [
-      'hook_info',
-      'install',
-      'module_implements_alter',
-      'requirements',
-      'schema',
-      'uninstall',
-      'update_last_removed',
-      'install_tasks',
-      'install_tasks_alter',
-    ];
-
-    if (in_array($hook->hook, $staticDenyHooks) || preg_match('/^(post_update_|preprocess_|update_\d+$)/', $hook->hook)) {
-      throw new \LogicException("The hook $hook->hook on class $class does not support attributes and must remain procedural.");
-    }
+    $module_list = $container->getParameter('container.modules');
+    $parameters = $container->getParameterBag()->all();
+    $skip_procedural_modules = array_filter(
+      array_keys($module_list),
+      fn (string $module) => !empty($parameters["$module.hooks_converted"]),
+    );
+    $collector = HookCollector::collectAllHookImplementations($module_list, $skip_procedural_modules);
+
+    $collector->writeToContainer($container);
   }
 
 }
diff --git a/core/lib/Drupal/Core/Hook/HookOperation.php b/core/lib/Drupal/Core/Hook/HookOperation.php
new file mode 100644
index 0000000000000000000000000000000000000000..f212588a42c631458377963b909b681d80b73de3
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/HookOperation.php
@@ -0,0 +1,36 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook;
+
+/**
+ * Base class for attributes that affect or define hook implementations.
+ *
+ * @internal
+ */
+abstract class HookOperation {
+
+  /**
+   * Constructs a HookOperation object.
+   *
+   * @param string $hook
+   *   The hook being implemented or modified.
+   * @param string $method
+   *   The method for the hook being implemented or modified.
+   *   This is required when modifying existing hook implementations it is
+   *   optional otherwise. See \Drupal\Core\Hook\Attribute\Hook for more
+   *   information.
+   * @param class-string $class
+   *   (optional) The class of the hook being implemented or modified.
+   * @param \Drupal\Core\Hook\Order|\Drupal\Core\Hook\ComplexOrder|null $order
+   *   (optional) Set the order of the hook referenced.
+   */
+  public function __construct(
+    public string $hook,
+    public string $method,
+    public ?string $class = NULL,
+    public Order|ComplexOrder|null $order = NULL,
+  ) {}
+
+}
diff --git a/core/lib/Drupal/Core/Hook/Order.php b/core/lib/Drupal/Core/Hook/Order.php
new file mode 100644
index 0000000000000000000000000000000000000000..f26ef257db72e980a4aafdee2d244a8e77b46d6b
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Order.php
@@ -0,0 +1,18 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook;
+
+/**
+ * Set this implementation to be first or last.
+ */
+enum Order: int {
+
+  // This implementation should fire first.
+  case First = 1;
+
+  // This implementation should fire last.
+  case Last = 0;
+
+}
diff --git a/core/lib/Drupal/Core/Hook/OrderAfter.php b/core/lib/Drupal/Core/Hook/OrderAfter.php
new file mode 100644
index 0000000000000000000000000000000000000000..be7811c1b79cdfdb32b3ceab76764a402d4d468f
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/OrderAfter.php
@@ -0,0 +1,17 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook;
+
+/**
+ * Set this implementation to be after others.
+ */
+readonly class OrderAfter extends ComplexOrder {
+
+  /**
+   * After means the priority should not be larger than others.
+   */
+  const bool VALUE = FALSE;
+
+}
diff --git a/core/lib/Drupal/Core/Hook/OrderBefore.php b/core/lib/Drupal/Core/Hook/OrderBefore.php
new file mode 100644
index 0000000000000000000000000000000000000000..4b1a1df6208b781fa061a66af3e2bfe1d0c0b505
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/OrderBefore.php
@@ -0,0 +1,17 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook;
+
+/**
+ * Set this implementation to be before others.
+ */
+readonly class OrderBefore extends ComplexOrder {
+
+  /**
+   * Before means the priority should be larger than others.
+   */
+  const bool VALUE = TRUE;
+
+}
diff --git a/core/modules/ckeditor5/ckeditor5.module b/core/modules/ckeditor5/ckeditor5.module
index 551ff7f397eaf66128a296d7653025adb4f26b16..b8db617e090d9661daef2617fc573b82a13cf778 100644
--- a/core/modules/ckeditor5/ckeditor5.module
+++ b/core/modules/ckeditor5/ckeditor5.module
@@ -17,31 +17,6 @@
 use Drupal\Core\Ajax\RemoveCommand;
 use Drupal\Core\Form\FormStateInterface;
 
-/**
- * Implements hook_module_implements_alter().
- */
-function ckeditor5_module_implements_alter(&$implementations, $hook): void {
-  // This module's implementation of form_filter_format_form_alter() must happen
-  // after the editor module's implementation, as that implementation adds the
-  // active editor to $form_state. It must also happen after the media module's
-  // implementation so media_filter_format_edit_form_validate can be removed
-  // from the validation chain, as that validator is not needed with CKEditor 5
-  // and will trigger a false error.
-  if ($hook === 'form_alter' && isset($implementations['ckeditor5']) && isset($implementations['editor'])) {
-    $group = $implementations['ckeditor5'];
-    unset($implementations['ckeditor5']);
-
-    $offset = array_search('editor', array_keys($implementations)) + 1;
-    if (array_key_exists('media', $implementations)) {
-      $media_offset = array_search('media', array_keys($implementations)) + 1;
-      $offset = max([$offset, $media_offset]);
-    }
-    $implementations = array_slice($implementations, 0, $offset, TRUE) +
-      ['ckeditor5' => $group] +
-      array_slice($implementations, $offset, NULL, TRUE);
-  }
-}
-
 /**
  * Form submission handler for filter format forms.
  */
diff --git a/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php b/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php
index 85ead6ae51dd6ab134247ab3adccc34e2869de3f..0f0b937f30b531fc8d799dafea8edfa6c7ab2fa5 100644
--- a/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php
+++ b/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\ckeditor5\Hook;
 
+use Drupal\Core\Hook\OrderAfter;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Asset\AttachedAssetsInterface;
 use Drupal\Core\Render\Element;
@@ -100,8 +101,20 @@ public function theme() : array {
 
   /**
    * Implements hook_form_FORM_ID_alter().
+   *
+   * This module's implementation of form_filter_format_form_alter() must
+   * happen after the editor module's implementation, as that implementation
+   * adds the active editor to $form_state. It must also happen after the media
+   * module's implementation so media_filter_format_edit_form_validate can be
+   * removed from the validation chain, as that validator is not needed with
+   * CKEditor 5 and will trigger a false error.
    */
-  #[Hook('form_filter_format_form_alter')]
+  #[Hook('form_filter_format_form_alter',
+    order: new OrderAfter(
+      modules: ['editor', 'media'],
+      extraTypes: ['form_filter_format_add_form_alter', 'form_filter_format_edit_form_alter'],
+    )
+  )]
   public function formFilterFormatFormAlter(array &$form, FormStateInterface $form_state, $form_id) : void {
     $editor = $form_state->get('editor');
     // CKEditor 5 plugin config determines the available HTML tags. If an HTML
diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index fd038312f285b59a9530d7dc45ce9c63c1094069..ee1ffac87cbaab78220b1603066431a5fa62baf5 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -10,28 +10,6 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageInterface;
 
-/**
- * Implements hook_module_implements_alter().
- */
-function content_translation_module_implements_alter(&$implementations, $hook): void {
-  switch ($hook) {
-    // Move our hook_entity_type_alter() implementation to the end of the list.
-    case 'entity_type_alter':
-      $group = $implementations['content_translation'];
-      unset($implementations['content_translation']);
-      $implementations['content_translation'] = $group;
-      break;
-
-    // Move our hook_entity_bundle_info_alter() implementation to the top of the
-    // list, so that any other hook implementation can rely on bundles being
-    // correctly marked as translatable.
-    case 'entity_bundle_info_alter':
-      $group = $implementations['content_translation'];
-      $implementations = ['content_translation' => $group] + $implementations;
-      break;
-  }
-}
-
 /**
  * Installs Content Translation's fields for a given entity type.
  *
diff --git a/core/modules/content_translation/src/Hook/ContentTranslationHooks.php b/core/modules/content_translation/src/Hook/ContentTranslationHooks.php
index 0f38a7f005dbd98859021152abf987b804531f5c..089b0a5ffba3a9647843e5f0af42fce4d83ebf95 100644
--- a/core/modules/content_translation/src/Hook/ContentTranslationHooks.php
+++ b/core/modules/content_translation/src/Hook/ContentTranslationHooks.php
@@ -17,6 +17,7 @@
 use Drupal\Core\Url;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Order;
 
 /**
  * Hook implementations for content_translation.
@@ -139,7 +140,7 @@ public function languageTypesInfoAlter(array &$language_types): void {
    *
    * @see \Drupal\Core\Entity\Annotation\EntityType
    */
-  #[Hook('entity_type_alter')]
+  #[Hook('entity_type_alter', order: Order::Last)]
   public function entityTypeAlter(array &$entity_types) : void {
     // Provide defaults for translation info.
     /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
@@ -221,7 +222,7 @@ public function languageContentSettingsUpdate(ContentLanguageSettingsInterface $
   /**
    * Implements hook_entity_bundle_info_alter().
    */
-  #[Hook('entity_bundle_info_alter')]
+  #[Hook('entity_bundle_info_alter', order: Order::First)]
   public function entityBundleInfoAlter(&$bundles): void {
     /** @var \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager */
     $content_translation_manager = \Drupal::service('content_translation.manager');
diff --git a/core/modules/layout_builder/layout_builder.module b/core/modules/layout_builder/layout_builder.module
index 32ce8bee9459aefefe55605965124c5c3ee860aa..37dc492bf7877a84d632a47cad9f82b387a7c647 100644
--- a/core/modules/layout_builder/layout_builder.module
+++ b/core/modules/layout_builder/layout_builder.module
@@ -6,20 +6,6 @@
 
 use Drupal\layout_builder\Plugin\SectionStorage\OverridesSectionStorage;
 
-/**
- * Implements hook_module_implements_alter().
- */
-function layout_builder_module_implements_alter(&$implementations, $hook): void {
-  if ($hook === 'entity_view_alter') {
-    // Ensure that this module's implementation of hook_entity_view_alter() runs
-    // last so that other modules that use this hook to render extra fields will
-    // run before it.
-    $group = $implementations['layout_builder'];
-    unset($implementations['layout_builder']);
-    $implementations['layout_builder'] = $group;
-  }
-}
-
 /**
  * Implements hook_preprocess_HOOK() for language-content-settings-table.html.twig.
  */
diff --git a/core/modules/layout_builder/src/Hook/LayoutBuilderHooks.php b/core/modules/layout_builder/src/Hook/LayoutBuilderHooks.php
index d12f9f0bc6846a8b60b49359eb6586429106030a..5be94e5e80ee7647bb7ee16aeb0b7ff3a7dc8a85 100644
--- a/core/modules/layout_builder/src/Hook/LayoutBuilderHooks.php
+++ b/core/modules/layout_builder/src/Hook/LayoutBuilderHooks.php
@@ -26,6 +26,7 @@
 use Drupal\Core\Url;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Order;
 
 /**
  * Hook implementations for layout_builder.
@@ -151,7 +152,7 @@ public function fieldConfigDelete(FieldConfigInterface $field_config): void {
    * @see \Drupal\layout_builder\Plugin\Block\ExtraFieldBlock::build()
    * @see layout_builder_module_implements_alter()
    */
-  #[Hook('entity_view_alter')]
+  #[Hook('entity_view_alter', order: Order::Last)]
   public function entityViewAlter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
     // Only replace extra fields when Layout Builder has been used to alter the
     // build. See \Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay::buildMultiple().
diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.module b/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.module
index de2e5316d073037b1f7c5069ef07cbec9102bc37..d7dda399be396db85e6a1ad3425c76dc4bb18656 100644
--- a/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.module
+++ b/core/modules/layout_builder/tests/modules/layout_builder_test/layout_builder_test.module
@@ -49,17 +49,3 @@ function layout_builder_test_preprocess_layout__twocol_section(&$vars): void {
     ];
   }
 }
-
-/**
- * Implements hook_module_implements_alter().
- */
-function layout_builder_test_module_implements_alter(&$implementations, $hook): void {
-  if ($hook === 'system_breadcrumb_alter') {
-    // Move our hook_system_breadcrumb_alter() implementation to run before
-    // layout_builder_system_breadcrumb_alter().
-    $group = $implementations['layout_builder_test'];
-    $implementations = [
-      'layout_builder_test' => $group,
-    ] + $implementations;
-  }
-}
diff --git a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Hook/LayoutBuilderTestHooks.php b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Hook/LayoutBuilderTestHooks.php
index 7a0d4797903557e34306ae6de42bd2166401d4fc..5a2a09729bcd72acff1fa8e72f9a634b360250d9 100644
--- a/core/modules/layout_builder/tests/modules/layout_builder_test/src/Hook/LayoutBuilderTestHooks.php
+++ b/core/modules/layout_builder/tests/modules/layout_builder_test/src/Hook/LayoutBuilderTestHooks.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\OrderBefore;
 
 /**
  * Hook implementations for layout_builder_test.
@@ -115,7 +116,12 @@ public function layoutBuilderEntityFormDisplayAlter(EntityFormDisplayInterface $
   /**
    * Implements hook_system_breadcrumb_alter().
    */
-  #[Hook('system_breadcrumb_alter')]
+  #[Hook(
+    'system_breadcrumb_alter',
+    order: new OrderBefore(
+      modules: ['layout_builder']
+    )
+  )]
   public function systemBreadcrumbAlter(Breadcrumb &$breadcrumb, RouteMatchInterface $route_match, array $context): void {
     $breadcrumb->addLink(Link::fromTextAndUrl('External link', Url::fromUri('http://www.example.com')));
   }
diff --git a/core/modules/navigation/navigation.module b/core/modules/navigation/navigation.module
index dbc11f191e641f276c8f4d2a309f786b90542ee9..570f13ba3e4a570c626b8a25159772dc70071bbe 100644
--- a/core/modules/navigation/navigation.module
+++ b/core/modules/navigation/navigation.module
@@ -6,21 +6,6 @@
 
 use Drupal\navigation\TopBarRegion;
 
-/**
- * Implements hook_module_implements_alter().
- */
-function navigation_module_implements_alter(&$implementations, $hook): void {
-  if ($hook == 'page_top') {
-    $group = $implementations['navigation'];
-    unset($implementations['navigation']);
-    $implementations['navigation'] = $group;
-  }
-  if ($hook == 'help') {
-    // We take over the layout_builder hook_help().
-    unset($implementations['layout_builder']);
-  }
-}
-
 /**
  * Prepares variables for navigation top bar template.
  *
diff --git a/core/modules/navigation/src/Hook/NavigationHooks.php b/core/modules/navigation/src/Hook/NavigationHooks.php
index 11bf1773dfe365b9b410c9995bc5adea6a8ae1a0..da19ec2dc768f2b6b41d9186a6040e983b81e452 100644
--- a/core/modules/navigation/src/Hook/NavigationHooks.php
+++ b/core/modules/navigation/src/Hook/NavigationHooks.php
@@ -7,9 +7,12 @@
 use Drupal\Core\Config\Action\ConfigActionManager;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Attribute\RemoveHook;
+use Drupal\Core\Hook\Order;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\layout_builder\Hook\LayoutBuilderHooks;
 use Drupal\navigation\NavigationContentLinks;
 use Drupal\navigation\NavigationRenderer;
 use Drupal\navigation\Plugin\SectionStorage\NavigationSectionStorage;
@@ -55,6 +58,7 @@ public function __construct(
    * Implements hook_help().
    */
   #[Hook('help')]
+  #[RemoveHook('help', class: LayoutBuilderHooks::class, method: 'help')]
   public function help($route_name, RouteMatchInterface $route_match): ?string {
     switch ($route_name) {
       case 'help.page.navigation':
@@ -79,7 +83,7 @@ public function help($route_name, RouteMatchInterface $route_match): ?string {
   /**
    * Implements hook_page_top().
    */
-  #[Hook('page_top')]
+  #[Hook('page_top', order: Order::Last)]
   public function pageTop(array &$page_top): void {
     if (!$this->currentUser->hasPermission('access navigation')) {
       return;
diff --git a/core/modules/system/tests/modules/common_test/common_test.module b/core/modules/system/tests/modules/common_test/common_test.module
index 7eaeca68fb66ec5ef82013cc20ea995bd4460f75..cc4d26756d1d399bd42cb71fb7f6a3062ff6a5f8 100644
--- a/core/modules/system/tests/modules/common_test/common_test.module
+++ b/core/modules/system/tests/modules/common_test/common_test.module
@@ -41,24 +41,6 @@ function olivero_drupal_alter_alter(&$data, &$arg2 = NULL, &$arg3 = NULL): void
   }
 }
 
-/**
- * Implements hook_module_implements_alter().
- *
- * @see block_drupal_alter_foo_alter()
- */
-function common_test_module_implements_alter(&$implementations, $hook): void {
-  // For
-  // \Drupal::moduleHandler()->alter(['drupal_alter', 'drupal_alter_foo'], ...),
-  // make the block module implementations run after all the other modules. Note
-  // that when \Drupal::moduleHandler->alter() is called with an array of types,
-  // the first type is considered primary and controls the module order.
-  if ($hook == 'drupal_alter_alter' && isset($implementations['block'])) {
-    $group = $implementations['block'];
-    unset($implementations['block']);
-    $implementations['block'] = $group;
-  }
-}
-
 /**
  * Implements MODULE_preprocess().
  *
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/hook_order_first_alphabetically.info.yml b/core/modules/system/tests/modules/hook_order_first_alphabetically/hook_order_first_alphabetically.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bd8dc0b2f73d53bcd3aa582e9fe718cee52c7b60
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/hook_order_first_alphabetically.info.yml
@@ -0,0 +1,7 @@
+name: first alphabetically
+type: module
+description: 'Test module used to test hook ordering.'
+package: Testing
+version: VERSION
+core_version_requirement: '*'
+hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php
new file mode 100644
index 0000000000000000000000000000000000000000..9dd41b0cbf06cffbc64facbbf5d6fa12d9ef6908
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php
@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_first_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\OrderAfter;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering permutation.
+ */
+class TestHookAfter {
+
+  /**
+   * This pair tests OrderAfter.
+   */
+  #[Hook('custom_hook_test_hook_after', order: new OrderAfter(['hook_order_last_alphabetically']))]
+  public static function hookAfter(): void {
+    $GLOBALS['HookAfter'] = 'HookAfter';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookBefore.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookBefore.php
new file mode 100644
index 0000000000000000000000000000000000000000..3b7ce8df15acdeecc338f242c8d7a672d90ed881
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookBefore.php
@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_first_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering permutation.
+ */
+class TestHookBefore {
+
+  /**
+   * This pair tests OrderBefore.
+   */
+  #[Hook('custom_hook_test_hook_before')]
+  public static function hookBefore(): void {
+    // This should be run after so HookBefore should not be set.
+    if (!isset($GLOBALS['HookBefore'])) {
+      $GLOBALS['HookOutOfOrderTestingHookBefore'] = 'HookOutOfOrderTestingHookBefore';
+    }
+    $GLOBALS['HookRanTestingHookBefore'] = 'HookRanTestingHookBefore';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookFirst.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookFirst.php
new file mode 100644
index 0000000000000000000000000000000000000000..31841f75549ab188bf23dead916fbe646e64832f
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookFirst.php
@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_first_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering permutation.
+ */
+class TestHookFirst {
+
+  /**
+   * This pair tests OrderFirst.
+   */
+  #[Hook('custom_hook_test_hook_first')]
+  public static function hookFirst(): void {
+    // This should be run after so HookFirst should not be set.
+    if (!isset($GLOBALS['HookFirst'])) {
+      $GLOBALS['HookOutOfOrderTestingHookFirst'] = 'HookOutOfOrderTestingHookFirst';
+    }
+    $GLOBALS['HookRanTestingHookFirst'] = 'HookRanTestingHookFirst';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php
new file mode 100644
index 0000000000000000000000000000000000000000..b93fd5155a62616621be922a24c6f2326ff887cd
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php
@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_first_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Order;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering permutation.
+ */
+class TestHookLast {
+
+  /**
+   * This pair tests OrderLast.
+   */
+  #[Hook('custom_hook_test_hook_last', order: Order::Last)]
+  public static function hookLast(): void {
+    $GLOBALS['HookLast'] = 'HookLast';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderExtraTypes.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderExtraTypes.php
new file mode 100644
index 0000000000000000000000000000000000000000..321cc19e5bf1028c111018f7ad31a7f7d28efd83
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderExtraTypes.php
@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_first_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\OrderAfter;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering permutation.
+ */
+class TestHookOrderExtraTypes {
+
+  /**
+   * This pair tests OrderAfter with ExtraTypes.
+   */
+  #[Hook('custom_hook_extra_types1_alter',
+    order: new OrderAfter(
+      modules: ['hook_order_last_alphabetically'],
+      extraTypes: ['custom_hook_extra_types2_alter'],
+    )
+  )]
+  public static function customHookExtraTypes(): void {
+    // This should be run after so HookOrderExtraTypes should not be set.
+    if (!isset($GLOBALS['HookOrderExtraTypes'])) {
+      $GLOBALS['HookOutOfOrderTestingOrderExtraTypes'] = 'HookOutOfOrderTestingOrderExtraTypes';
+    }
+    $GLOBALS['HookRanTestingOrderExtraTypes'] = 'HookRanTestingOrderExtraTypes';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookReOrderHookFirst.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookReOrderHookFirst.php
new file mode 100644
index 0000000000000000000000000000000000000000..8e48435ebae15e7157bb5d4c4cbde974a3a4e591
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookReOrderHookFirst.php
@@ -0,0 +1,45 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_first_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\OrderAfter;
+use Drupal\Core\Hook\Attribute\ReOrderHook;
+use Drupal\hook_order_last_alphabetically\Hook\TestHookReOrderHookLast;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering permutation.
+ */
+class TestHookReOrderHookFirst {
+
+  /**
+   * This pair tests ReOrderHook.
+   */
+  #[Hook('custom_hook_override')]
+  #[ReOrderHook(
+    'custom_hook_override',
+    class: TestHookReOrderHookLast::class,
+    method: 'customHookOverride',
+    order: new OrderAfter(
+      classesAndMethods: [[TestHookReOrderHookFirst::class, 'customHookOverride']],
+    )
+  )]
+  public static function customHookOverride(): void {
+    // This normally would run first.
+    // We override that order in hook_order_second_alphabetically.
+    // We override, that order here with ReOrderHook.
+    $GLOBALS['HookRanTestingReOrderHookFirstAlpha'] = 'HookRanTestingReOrderHookFirstAlpha';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/hook_order_last_alphabetically.info.yml b/core/modules/system/tests/modules/hook_order_last_alphabetically/hook_order_last_alphabetically.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..aa08f259dc2e7ef063428315292bb69c00fb8714
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/hook_order_last_alphabetically.info.yml
@@ -0,0 +1,7 @@
+name: Hook ordering last
+type: module
+description: 'Test module used to test hook ordering.'
+package: Testing
+version: VERSION
+core_version_requirement: '*'
+hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookAfter.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookAfter.php
new file mode 100644
index 0000000000000000000000000000000000000000..c132f940c319072103f5f89f84d2af1a64ada9e6
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookAfter.php
@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_last_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering permutation.
+ */
+class TestHookAfter {
+
+  /**
+   * This pair tests OrderAfter.
+   */
+  #[Hook('custom_hook_test_hook_after')]
+  public static function hookAfter(): void {
+    // This should be run before so HookAfter should not be set.
+    if (isset($GLOBALS['HookAfter'])) {
+      $GLOBALS['HookOutOfOrderTestingHookAfter'] = 'HookOutOfOrderTestingHookAfter';
+    }
+    $GLOBALS['HookRanTestingHookAfter'] = 'HookRanTestingHookAfter';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookBefore.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookBefore.php
new file mode 100644
index 0000000000000000000000000000000000000000..03b19b10ffe971ba291cce9af3582f9436008f22
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookBefore.php
@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_last_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\OrderBefore;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering permutation.
+ */
+class TestHookBefore {
+
+  /**
+   * This pair tests OrderBefore.
+   */
+  #[Hook('custom_hook_test_hook_before', order: new OrderBefore(['hook_order_first_alphabetically']))]
+  public static function cacheFlush(): void {
+    $GLOBALS['HookBefore'] = 'HookBefore';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookFirst.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookFirst.php
new file mode 100644
index 0000000000000000000000000000000000000000..ee659a32585f10580c6d01b35f4cbbf847137eb1
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookFirst.php
@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_last_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Order;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering permutation.
+ */
+class TestHookFirst {
+
+  /**
+   * This pair tests OrderFirst.
+   */
+  #[Hook('custom_hook_test_hook_first', order: Order::First)]
+  public static function hookFirst(): void {
+    $GLOBALS['HookFirst'] = 'HookFirst';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookLast.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookLast.php
new file mode 100644
index 0000000000000000000000000000000000000000..36b32f08678bd9a53c3d867690a8f3d586cedd2a
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookLast.php
@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_last_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering permutation.
+ */
+class TestHookLast {
+
+  /**
+   * This pair tests OrderLast.
+   */
+  #[Hook('custom_hook_test_hook_last')]
+  public static function hookLast(): void {
+    // This should be run before so HookLast should not be set.
+    if (isset($GLOBALS['HookLast'])) {
+      $GLOBALS['HookOutOfOrderTestingHookLast'] = 'HookOutOfOrderTestingHookLast';
+    }
+    $GLOBALS['HookRanTestingHookLast'] = 'HookRanTestingHookLast';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderExtraTypes.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderExtraTypes.php
new file mode 100644
index 0000000000000000000000000000000000000000..2ca8a0d93f42eb0cd61dd670ca13ab113361f131
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderExtraTypes.php
@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_last_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering permutation.
+ */
+class TestHookOrderExtraTypes {
+
+  /**
+   * This pair tests OrderAfter with ExtraTypes.
+   */
+  #[Hook('custom_hook_extra_types2_alter')]
+  public static function customHookExtraTypes(): void {
+    $GLOBALS['HookOrderExtraTypes'] = 'HookOrderExtraTypes';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookReOrderHookLast.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookReOrderHookLast.php
new file mode 100644
index 0000000000000000000000000000000000000000..a46e3583609ec1191e41cde04bf716b8c8342769
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookReOrderHookLast.php
@@ -0,0 +1,39 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_last_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Order;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering permutation.
+ */
+class TestHookReOrderHookLast {
+
+  /**
+   * This pair tests ReOrderHook.
+   */
+  #[Hook('custom_hook_override', order: Order::First)]
+  public static function customHookOverride(): void {
+    // This normally would run second.
+    // We override that order here with Order::First.
+    // We override, that order in hook_order_first_alphabetically with
+    // ReOrderHook.
+    if (!isset($GLOBALS['HookRanTestingReOrderHookFirstAlpha'])) {
+      $GLOBALS['HookOutOfOrderTestingReOrderHook'] = 'HookOutOfOrderTestingReOrderHook';
+    }
+    $GLOBALS['HookRanTestingReOrderHookLastAlpha'] = 'HookRanTestingReOrderHookLastAlpha';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_second_order_first_alphabetically/hook_second_order_first_alphabetically.info.yml b/core/modules/system/tests/modules/hook_second_order_first_alphabetically/hook_second_order_first_alphabetically.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..bd8dc0b2f73d53bcd3aa582e9fe718cee52c7b60
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_second_order_first_alphabetically/hook_second_order_first_alphabetically.info.yml
@@ -0,0 +1,7 @@
+name: first alphabetically
+type: module
+description: 'Test module used to test hook ordering.'
+package: Testing
+version: VERSION
+core_version_requirement: '*'
+hidden: true
diff --git a/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php b/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php
new file mode 100644
index 0000000000000000000000000000000000000000..b373391735782959a5404f5a2925e135b387dd24
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php
@@ -0,0 +1,37 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_second_order_first_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\OrderAfter;
+use Drupal\hook_second_order_last_alphabetically\Hook\TestHookAfterClassMethod as TestHookAfterClassMethodForAfter;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering permutation.
+ */
+class TestHookAfterClassMethod {
+
+  /**
+   * This pair tests OrderAfter with a passed class and method.
+   */
+  #[Hook('custom_hook_test_hook_after_class_method',
+    order: new OrderAfter(
+      classesAndMethods: [[TestHookAfterClassMethodForAfter::class, 'hookAfterClassMethod']],
+    )
+  )]
+  public static function hookAfterClassMethod(): void {
+    $GLOBALS['HookAfterClassMethod'] = 'HookAfterMethod';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_second_order_last_alphabetically/hook_second_order_last_alphabetically.info.yml b/core/modules/system/tests/modules/hook_second_order_last_alphabetically/hook_second_order_last_alphabetically.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..aa08f259dc2e7ef063428315292bb69c00fb8714
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_second_order_last_alphabetically/hook_second_order_last_alphabetically.info.yml
@@ -0,0 +1,7 @@
+name: Hook ordering last
+type: module
+description: 'Test module used to test hook ordering.'
+package: Testing
+version: VERSION
+core_version_requirement: '*'
+hidden: true
diff --git a/core/modules/system/tests/modules/hook_second_order_last_alphabetically/src/Hook/TestHookAfterClassMethod.php b/core/modules/system/tests/modules/hook_second_order_last_alphabetically/src/Hook/TestHookAfterClassMethod.php
new file mode 100644
index 0000000000000000000000000000000000000000..d69aaa546fb0acd3a4cd5930a93757821b58b19a
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_second_order_last_alphabetically/src/Hook/TestHookAfterClassMethod.php
@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_second_order_last_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering permutation.
+ */
+class TestHookAfterClassMethod {
+
+  /**
+   * This pair tests #[HookAfter].
+   */
+  #[Hook('custom_hook_test_hook_after_class_method')]
+  public static function hookAfterClassMethod(): void {
+    // This should be run before so HookAfter should not be set.
+    if (isset($GLOBALS['HookAfterClassMethod'])) {
+      $GLOBALS['HookOutOfOrderTestingHookAfterClassMethod'] = 'HookOutOfOrderTestingHookAfterClassMethod';
+    }
+    $GLOBALS['HookRanTestingHookAfterClassMethod'] = 'HookRanTestingHookAfterClassMethod';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_test_remove/hook_test_remove.info.yml b/core/modules/system/tests/modules/hook_test_remove/hook_test_remove.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9245dd4208f7cc17a6eccfe380218e5c09b4463e
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_test_remove/hook_test_remove.info.yml
@@ -0,0 +1,7 @@
+name: Hook test removal
+type: module
+description: 'Test module used to test hook removal.'
+package: Testing
+version: VERSION
+core_version_requirement: '*'
+hidden: true
diff --git a/core/modules/system/tests/modules/hook_test_remove/src/Hook/TestHookRemove.php b/core/modules/system/tests/modules/hook_test_remove/src/Hook/TestHookRemove.php
new file mode 100644
index 0000000000000000000000000000000000000000..3ea922ec36ce618149c0358758daa0f7bb8358e0
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_test_remove/src/Hook/TestHookRemove.php
@@ -0,0 +1,36 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_test_remove\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Attribute\RemoveHook;
+
+/**
+ * Add a hook here, then remove it with another attribute.
+ */
+class TestHookRemove {
+
+  /**
+   * This hook should not be run because the next hook replaces it.
+   */
+  #[Hook('custom_hook1')]
+  public static function hookDoNotRun(): void {
+    $GLOBALS['HookShouldNotRunTestRemove'] = 'HookShouldNotRunTestRemove';
+  }
+
+  /**
+   * This hook should run and prevent custom_hook1.
+   */
+  #[Hook('custom_hook2')]
+  #[RemoveHook(
+    'custom_hook1',
+    class: TestHookRemove::class,
+    method: 'hookDoNotRun'
+  )]
+  public static function hookDoRun(): void {
+    $GLOBALS['HookShouldRunTestRemove'] = 'HookShouldRunTestRemove';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/module_implements_alter_test/module_implements_alter_test.implementations.inc b/core/modules/system/tests/modules/module_implements_alter_test/module_implements_alter_test.implementations.inc
new file mode 100644
index 0000000000000000000000000000000000000000..7b6bac4ae95dc2d23564c4b807dfa352a3e263be
--- /dev/null
+++ b/core/modules/system/tests/modules/module_implements_alter_test/module_implements_alter_test.implementations.inc
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @file
+ * Include file for test module.
+ */
+
+declare(strict_types=1);
+
+/**
+ * Implements hook_altered_test_hook().
+ *
+ * @see module_implements_alter_test_module_implements_alter()
+ */
+function module_implements_alter_test_altered_test_hook(): string {
+  return __FUNCTION__;
+}
diff --git a/core/modules/system/tests/modules/module_implements_alter_test/module_implements_alter_test.info.yml b/core/modules/system/tests/modules/module_implements_alter_test/module_implements_alter_test.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..25995b17cd9aac88bd987ec1e491499e20fa0f65
--- /dev/null
+++ b/core/modules/system/tests/modules/module_implements_alter_test/module_implements_alter_test.info.yml
@@ -0,0 +1,6 @@
+name: 'Test hook_module_implements_alter'
+type: module
+description: 'Support module for module system testing.'
+package: Testing
+version: VERSION
+core_version_requirement: '*'
diff --git a/core/modules/system/tests/modules/module_implements_alter_test/module_implements_alter_test.module b/core/modules/system/tests/modules/module_implements_alter_test/module_implements_alter_test.module
new file mode 100644
index 0000000000000000000000000000000000000000..6e7452988600873488d1a5da7cf5320150586f92
--- /dev/null
+++ b/core/modules/system/tests/modules/module_implements_alter_test/module_implements_alter_test.module
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Module file for test module.
+ */
+
+declare(strict_types=1);
+
+function test_auto_include(): void {}
+
+/**
+ * Implements hook_module_implements_alter().
+ *
+ * @see \Drupal\system\Tests\Module\ModuleImplementsAlterTest::testModuleImplementsAlter()
+ * @see module_implements_alter_test_module_implements_alter()
+ */
+function module_implements_alter_test_module_implements_alter(&$implementations, $hook): void {
+  if ($hook === 'altered_test_hook') {
+    // Add a hook implementation, that will be found in
+    // module_implements_alter_test.implementation.inc.
+    $implementations['module_implements_alter_test'] = 'implementations';
+  }
+  if ($hook === 'unimplemented_test_hook') {
+    // Add the non-existing function module_implements_alter_test_unimplemented_test_hook(). This
+    // should cause an exception to be thrown in
+    // \Drupal\Core\Extension\ModuleHandler::buildImplementationInfo('unimplemented_test_hook').
+    $implementations['module_implements_alter_test'] = FALSE;
+  }
+
+  // For
+  // \Drupal::moduleHandler()->alter(['drupal_alter', 'drupal_alter_foo'], ...),
+  // make the block module implementations run after all the other modules. Note
+  // that when \Drupal::moduleHandler->alter() is called with an array of types,
+  // the first type is considered primary and controls the module order.
+  if ($hook == 'drupal_alter_alter' && isset($implementations['block'])) {
+    $group = $implementations['block'];
+    unset($implementations['block']);
+    $implementations['block'] = $group;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/module_test/module_test.module b/core/modules/system/tests/modules/module_test/module_test.module
index b7f320a35e3c26b8f704cf593cad67d66e640ecf..35561f09c8a0c0dde33fea382604af5029ea9d90 100644
--- a/core/modules/system/tests/modules/module_test/module_test.module
+++ b/core/modules/system/tests/modules/module_test/module_test.module
@@ -95,23 +95,3 @@ function module_test_modules_uninstalled($modules): void {
   // can check that the modules were uninstalled in the correct sequence.
   \Drupal::state()->set('module_test.uninstall_order', $modules);
 }
-
-/**
- * Implements hook_module_implements_alter().
- *
- * @see module_test_altered_test_hook()
- * @see \Drupal\system\Tests\Module\ModuleImplementsAlterTest::testModuleImplementsAlter()
- */
-function module_test_module_implements_alter(&$implementations, $hook): void {
-  if ($hook === 'altered_test_hook') {
-    // Add a hook implementation, that will be found in
-    // module_test.implementation.inc.
-    $implementations['module_test'] = 'implementations';
-  }
-  if ($hook === 'unimplemented_test_hook') {
-    // Add the non-existing function module_test_unimplemented_test_hook(). This
-    // should cause an exception to be thrown in
-    // \Drupal\Core\Extension\ModuleHandler::buildImplementationInfo('unimplemented_test_hook').
-    $implementations['module_test'] = FALSE;
-  }
-}
diff --git a/core/modules/system/tests/src/Kernel/Common/AlterTest.php b/core/modules/system/tests/src/Kernel/Common/AlterTest.php
index 18217579ad952cc5691eb18bd87354a8d69507bc..0ff2115214f4564c8d1870e165c01bc9f968f70e 100644
--- a/core/modules/system/tests/src/Kernel/Common/AlterTest.php
+++ b/core/modules/system/tests/src/Kernel/Common/AlterTest.php
@@ -19,11 +19,14 @@ class AlterTest extends KernelTestBase {
   protected static $modules = [
     'block',
     'common_test',
+    'module_implements_alter_test',
     'system',
   ];
 
   /**
    * Tests if the theme has been altered.
+   *
+   * @group legacy
    */
   public function testDrupalAlter(): void {
     // This test depends on Olivero, so make sure that it is always the current
diff --git a/core/modules/user/tests/modules/user_hooks_test/user_hooks_test.info.yml b/core/modules/user/tests/modules/user_hooks_test/user_hooks_test.info.yml
old mode 100644
new mode 100755
diff --git a/core/modules/workspaces/src/Hook/EntityOperations.php b/core/modules/workspaces/src/Hook/EntityOperations.php
index c459f4d065e3fbd47998ffa6941a561452993034..915499f685eb9fc56c9254edaa157f468de26650 100644
--- a/core/modules/workspaces/src/Hook/EntityOperations.php
+++ b/core/modules/workspaces/src/Hook/EntityOperations.php
@@ -12,6 +12,10 @@
 use Drupal\Core\Entity\RevisionableInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Attribute\ReOrderHook;
+use Drupal\Core\Hook\Order;
+use Drupal\Core\Hook\OrderBefore;
+use Drupal\content_moderation\Hook\ContentModerationHooks;
 use Drupal\workspaces\WorkspaceAssociationInterface;
 use Drupal\workspaces\WorkspaceInformationInterface;
 use Drupal\workspaces\WorkspaceManagerInterface;
@@ -72,7 +76,12 @@ public function entityPreload(array $ids, string $entity_type_id): array {
   /**
    * Implements hook_entity_presave().
    */
-  #[Hook('entity_presave')]
+  #[Hook('entity_presave', order: Order::First)]
+  #[ReOrderHook('entity_presave',
+    class: ContentModerationHooks::class,
+    method: 'entityPresave',
+    order: new OrderBefore(['workspaces'])
+  )]
   public function entityPresave(EntityInterface $entity): void {
     if ($this->shouldSkipOperations($entity)) {
       return;
@@ -129,7 +138,7 @@ public function entityPresave(EntityInterface $entity): void {
   /**
    * Implements hook_entity_insert().
    */
-  #[Hook('entity_insert')]
+  #[Hook('entity_insert', order: Order::Last)]
   public function entityInsert(EntityInterface $entity): void {
     if ($entity->getEntityTypeId() === 'workspace') {
       $this->workspaceAssociation->workspaceInsert($entity);
diff --git a/core/modules/workspaces/workspaces.module b/core/modules/workspaces/workspaces.module
deleted file mode 100644
index a053105f20c372f667e504ce4560b29fde55be14..0000000000000000000000000000000000000000
--- a/core/modules/workspaces/workspaces.module
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-
-/**
- * @file
- */
-
-/**
- * Implements hook_module_implements_alter().
- */
-function workspaces_module_implements_alter(&$implementations, $hook): void {
-  // Move our 'hook_entity_presave' implementation at the beginning to ensure
-  // that other presave implementations are aware of the changes done in
-  // \Drupal\workspaces\Hook\EntityOperations::entityPresave().
-  if ($hook === 'entity_presave') {
-    $implementation = $implementations['workspaces'];
-    $implementations = ['workspaces' => $implementation] + $implementations;
-
-    // Move Content Moderation's implementation before Workspaces, so we can
-    // alter the publishing status for the default revision.
-    if (isset($implementations['content_moderation'])) {
-      $implementation = $implementations['content_moderation'];
-      $implementations = ['content_moderation' => $implementation] + $implementations;
-    }
-  }
-
-  // Move our 'hook_entity_insert' implementation at the end to ensure that
-  // the second (pending) revision created for published entities is not used
-  // by other 'hook_entity_insert' implementations.
-  // @see \Drupal\workspaces\Hook\EntityOperations::entityInsert()
-  if ($hook === 'entity_insert') {
-    $group = $implementations['workspaces'];
-    unset($implementations['workspaces']);
-    $implementations['workspaces'] = $group;
-  }
-}
diff --git a/core/tests/Drupal/KernelTests/Core/Extension/ModuleImplementsAlterTest.php b/core/tests/Drupal/KernelTests/Core/Extension/ModuleImplementsAlterTest.php
index ef0022b1bfaf52b974e6aed47a2c9dd0f7ecea71..2e9447c49913733b648e7dc9c4799596bbfedbf0 100644
--- a/core/tests/Drupal/KernelTests/Core/Extension/ModuleImplementsAlterTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Extension/ModuleImplementsAlterTest.php
@@ -10,6 +10,8 @@
  * Tests hook_module_implements_alter().
  *
  * @group Module
+ *
+ * @group legacy
  */
 class ModuleImplementsAlterTest extends KernelTestBase {
 
@@ -22,7 +24,7 @@ class ModuleImplementsAlterTest extends KernelTestBase {
    * Tests hook_module_implements_alter() adding an implementation.
    *
    * @see \Drupal\Core\Extension\ModuleHandler::buildImplementationInfo()
-   * @see module_test_module_implements_alter()
+   * @see module_implements_alter_test_module_implements_alter()
    */
   public function testModuleImplementsAlter(): void {
 
@@ -32,38 +34,33 @@ public function testModuleImplementsAlter(): void {
 
     $this->assertSame(\Drupal::moduleHandler(), $module_handler, 'Module handler instance is still the same.');
 
-    // Install the module_test module.
-    \Drupal::service('module_installer')->install(['module_test']);
+    // Install the module_implements_alter_test module.
+    \Drupal::service('module_installer')->install(['module_implements_alter_test']);
 
     // Assert that the \Drupal::moduleHandler() instance has been replaced.
     $this->assertNotSame(\Drupal::moduleHandler(), $module_handler, 'The \Drupal::moduleHandler() instance has been replaced during \Drupal::moduleHandler()->install().');
 
-    // Assert that module_test.module is now included.
-    $this->assertTrue(function_exists('module_test_modules_installed'),
-      'The file module_test.module was successfully included.');
-
-    $this->assertArrayHasKey('module_test', \Drupal::moduleHandler()->getModuleList());
+    // Assert that module_implements_alter_test.module is now included.
+    $this->assertTrue(function_exists('test_auto_include'),
+    'The file module_implements_alter_test.module was successfully included.');
 
-    $this->assertTrue(\Drupal::moduleHandler()->hasImplementations('modules_installed', 'module_test'),
-      'module_test implements hook_modules_installed().');
+    $this->assertTrue(\Drupal::moduleHandler()->hasImplementations('module_implements_alter', 'module_implements_alter_test'),
+    'module_implements_alter_test implements hook_module_implements_alter().');
 
-    $this->assertTrue(\Drupal::moduleHandler()->hasImplementations('module_implements_alter', 'module_test'),
-      'module_test implements hook_module_implements_alter().');
-
-    // Assert that module_test.implementations.inc is not included yet.
-    $this->assertFalse(function_exists('module_test_altered_test_hook'),
-      'The file module_test.implementations.inc is not included yet.');
+    // Assert that module_implements_alter_test.implementations.inc is not included yet.
+    $this->assertFalse(function_exists('module_implements_alter_test_altered_test_hook'),
+      'The file module_implements_alter_test.implementations.inc is not included yet.');
 
     // Trigger hook discovery for hook_altered_test_hook().
-    // Assert that module_test_module_implements_alter(*, 'altered_test_hook')
+    // Assert that module_implements_alter_test_module_implements_alter(*, 'altered_test_hook')
     // has added an implementation.
-    $this->assertTrue(\Drupal::moduleHandler()->hasImplementations('altered_test_hook', 'module_test'),
-      'module_test implements hook_altered_test_hook().');
+    $this->assertTrue(\Drupal::moduleHandler()->hasImplementations('altered_test_hook', 'module_implements_alter_test'),
+      'module_implements_alter_test implements hook_altered_test_hook().');
+
+    // Assert that module_implements_alter_test.implementations.inc was included as part of the process.
+    $this->assertTrue(function_exists('module_implements_alter_test_altered_test_hook'),
+      'The file module_implements_alter_test.implementations.inc was included.');
 
-    // Assert that module_test.implementations.inc was included as part of the
-    // process.
-    $this->assertTrue(function_exists('module_test_altered_test_hook'),
-      'The file module_test.implementations.inc was included.');
   }
 
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
index 4156481d3b924a88d8fcf1b0953b80e1669b66c2..10765f3a084b2f4ae501384ad9b765e22aa821de 100644
--- a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
@@ -52,6 +52,8 @@ public function testSymlink(): void {
 
   /**
    * Test that ordering works.
+   *
+   * @group legacy
    */
   public function testOrdering(): void {
     $container = new ContainerBuilder();
@@ -82,6 +84,23 @@ public function testOrdering(): void {
     $this->assertLessThan($priorities['drupal_hook.order2']['order'], $priorities['drupal_hook.order2']['module_handler_test_all2_order2']);
   }
 
+  /**
+   * Test LegacyModuleImplementsAlter.
+   */
+  public function testLegacyModuleImplementsAlter(): void {
+    $container = new ContainerBuilder();
+    $module_filenames = [
+      'module_implements_alter_test_legacy' => ['pathname' => "core/tests/Drupal/Tests/Core/Extension/modules/module_implements_alter_test_legacy/module_implements_alter_test_legacy.info.yml"],
+    ];
+    include_once 'core/tests/Drupal/Tests/Core/Extension/modules/module_implements_alter_test_legacy/module_implements_alter_test_legacy.module';
+    $container->setParameter('container.modules', $module_filenames);
+    $container->setDefinition('module_handler', new Definition());
+    (new HookCollectorPass())->process($container);
+
+    // This test will also fail if the deprecation notice shows up.
+    $this->assertFalse(isset($GLOBALS['ShouldNotRunLegacyModuleImplementsAlter']));
+  }
+
   /**
    * Test hooks implemented on behalf of an uninstalled module.
    *
@@ -121,7 +140,6 @@ public function testProceduralHooksSkippedWhenConfigured(): void {
     $this->assertFalse(isset($GLOBALS['procedural_attribute_skip_after_attribute']));
     $this->assertTrue(isset($GLOBALS['procedural_attribute_skip_find']));
     $this->assertTrue(isset($GLOBALS['skipped_procedural_oop_cache_flush']));
-
   }
 
   /**
@@ -137,4 +155,151 @@ public function testHookAttribute(): void {
     $this->assertTrue(isset($GLOBALS['hook_invoke_method']));
   }
 
+  /**
+   * Tests hook ordering with attributes.
+   */
+  public function testHookFirst(): void {
+    $module_installer = $this->container->get('module_installer');
+    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically']));
+    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically']));
+    $this->assertFalse(isset($GLOBALS['HookFirst']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingHookFirst']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingHookFirst']));
+    $module_handler = $this->container->get('module_handler');
+    $data = ['hi'];
+    $module_handler->invokeAll('custom_hook_test_hook_first', $data);
+    $this->assertTrue(isset($GLOBALS['HookFirst']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingHookFirst']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingHookFirst']));
+  }
+
+  /**
+   * Tests hook ordering with attributes.
+   */
+  public function testHookAfter(): void {
+    $module_installer = $this->container->get('module_installer');
+    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically']));
+    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically']));
+    $this->assertFalse(isset($GLOBALS['HookAfter']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingHookAfter']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingHookAfter']));
+    $module_handler = $this->container->get('module_handler');
+    $data = ['hi'];
+    $module_handler->invokeAll('custom_hook_test_hook_after', $data);
+    $this->assertTrue(isset($GLOBALS['HookAfter']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingHookAfter']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingHookAfter']));
+  }
+
+  /**
+   * Tests hook ordering with attributes.
+   */
+  public function testHookAfterClassMethod(): void {
+    $module_installer = $this->container->get('module_installer');
+    $this->assertTrue($module_installer->install(['hook_second_order_first_alphabetically']));
+    $this->assertTrue($module_installer->install(['hook_second_order_last_alphabetically']));
+    $this->assertFalse(isset($GLOBALS['HookAfterClassMethod']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingHookAfterClassMethod']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingHookAfterClassMethod']));
+    $module_handler = $this->container->get('module_handler');
+    $data = ['hi'];
+    $module_handler->invokeAll('custom_hook_test_hook_after_class_method', $data);
+    $this->assertTrue(isset($GLOBALS['HookAfterClassMethod']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingHookAfterClassMethod']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingHookAfterClassMethod']));
+  }
+
+  /**
+   * Tests hook ordering with attributes.
+   */
+  public function testHookBefore(): void {
+    $module_installer = $this->container->get('module_installer');
+    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically']));
+    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically']));
+    $this->assertFalse(isset($GLOBALS['HookBefore']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingHookBefore']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingHookBefore']));
+    $module_handler = $this->container->get('module_handler');
+    $data = ['hi'];
+    $module_handler->invokeAll('custom_hook_test_hook_before', $data);
+    $this->assertTrue(isset($GLOBALS['HookBefore']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingHookBefore']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingHookBefore']));
+  }
+
+  /**
+   * Tests hook ordering with attributes.
+   */
+  public function testHookOrderExtraTypes(): void {
+    $module_installer = $this->container->get('module_installer');
+    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically']));
+    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically']));
+    $this->assertFalse(isset($GLOBALS['HookOrderExtraTypes']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingOrderExtraTypes']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingOrderExtraTypes']));
+    $module_handler = $this->container->get('module_handler');
+    $hooks = [
+      'custom_hook',
+      'custom_hook_extra_types1',
+      'custom_hook_extra_types2',
+    ];
+    $data = ['hi'];
+    $module_handler->alter($hooks, $data);
+    $this->assertTrue(isset($GLOBALS['HookOrderExtraTypes']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingOrderExtraTypes']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingOrderExtraTypes']));
+  }
+
+  /**
+   * Tests hook ordering with attributes.
+   */
+  public function testHookLast(): void {
+    $module_installer = $this->container->get('module_installer');
+    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically']));
+    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically']));
+    $this->assertFalse(isset($GLOBALS['HookLast']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingHookLast']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingHookLast']));
+    $module_handler = $this->container->get('module_handler');
+    $data = ['hi'];
+    $module_handler->invokeAll('custom_hook_test_hook_last', $data);
+    $this->assertTrue(isset($GLOBALS['HookLast']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingHookLast']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingHookLast']));
+  }
+
+  /**
+   * Tests hook remove.
+   */
+  public function testHookRemove(): void {
+    $module_installer = $this->container->get('module_installer');
+    $this->assertTrue($module_installer->install(['hook_test_remove']));
+    $this->assertFalse(isset($GLOBALS['HookShouldRunTestRemove']));
+    $this->assertFalse(isset($GLOBALS['HookShouldNotRunTestRemove']));
+    $module_handler = $this->container->get('module_handler');
+    $data = ['hi'];
+    $module_handler->invokeAll('custom_hook1', $data);
+    $module_handler->invokeAll('custom_hook2', $data);
+    $this->assertTrue(isset($GLOBALS['HookShouldRunTestRemove']));
+    $this->assertFalse(isset($GLOBALS['HookShouldNotRunTestRemove']));
+  }
+
+  /**
+   * Tests hook override.
+   */
+  public function testHookOverride(): void {
+    $module_installer = $this->container->get('module_installer');
+    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically']));
+    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingReOrderHookFirstAlpha']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingReOrderHook']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingReOrderHookLastAlpha']));
+    $module_handler = $this->container->get('module_handler');
+    $data = ['hi'];
+    $module_handler->invokeAll('custom_hook_override', $data);
+    $this->assertTrue(isset($GLOBALS['HookRanTestingReOrderHookFirstAlpha']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingReOrderHook']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingReOrderHookLastAlpha']));
+  }
+
 }
diff --git a/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php b/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php
index 500973836edec61d069c9b83d7eda0ae4d5e7740..143c56c542f8ae8a879347b3fbd515e73e78ddac 100644
--- a/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php
+++ b/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php
@@ -79,8 +79,11 @@ public function testLoadModule(): void {
    * Tests loading all modules.
    *
    * @covers ::loadAll
+   *
+   * @group legacy
    */
   public function testLoadAllModules(): void {
+    $this->expectDeprecation('module_handler_test_all1_module_implements_alter without a #[LegacyModuleImplementsAlter] attribute is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. See https://www.drupal.org/node/3496788');
     $module_handler = $this->getModuleHandler();
     $module_handler->addModule('module_handler_test_all1', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all1');
     $module_handler->addModule('module_handler_test_all2', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all2');
@@ -326,8 +329,11 @@ public function testImplementsHookModuleEnabled(): void {
    * Tests invoke all.
    *
    * @covers ::invokeAll
+   *
+   * @group legacy
    */
   public function testInvokeAll(): void {
+    $this->expectDeprecation('module_handler_test_all1_module_implements_alter without a #[LegacyModuleImplementsAlter] attribute is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. See https://www.drupal.org/node/3496788');
     $module_handler = $this->getModuleHandler();
     $module_handler->addModule('module_handler_test_all1', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all1');
     $module_handler->addModule('module_handler_test_all2', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all2');
diff --git a/core/tests/Drupal/Tests/Core/Extension/modules/module_implements_alter_test_legacy/module_implements_alter_test_legacy.info.yml b/core/tests/Drupal/Tests/Core/Extension/modules/module_implements_alter_test_legacy/module_implements_alter_test_legacy.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e286a4ffac38950580c6377db8e62592b94bf2b5
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Extension/modules/module_implements_alter_test_legacy/module_implements_alter_test_legacy.info.yml
@@ -0,0 +1,6 @@
+name: 'Module for testing LegacyModuleImplementsAlter'
+type: module
+description: 'Support module for module system testing.'
+package: Testing
+version: VERSION
+core_version_requirement: '*'
diff --git a/core/tests/Drupal/Tests/Core/Extension/modules/module_implements_alter_test_legacy/module_implements_alter_test_legacy.module b/core/tests/Drupal/Tests/Core/Extension/modules/module_implements_alter_test_legacy/module_implements_alter_test_legacy.module
new file mode 100644
index 0000000000000000000000000000000000000000..355b561694973eff8fdbab027339282c20d9dfe1
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Extension/modules/module_implements_alter_test_legacy/module_implements_alter_test_legacy.module
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * Module file for test module.
+ */
+
+declare(strict_types=1);
+
+use Drupal\Core\Hook\Attribute\LegacyModuleImplementsAlter;
+
+/**
+ * Implements hook_module_implements_alter().
+ *
+ * @see \Drupal\system\Tests\Module\ModuleImplementsAlterTest::testModuleImplementsAlter()
+ */
+#[LegacyModuleImplementsAlter]
+function module_implements_alter_test_legacy_module_implements_alter(&$implementations, $hook): void {
+  $GLOBALS['ShouldNotRunLegacyModuleImplementsAlter'] = TRUE;
+}
diff --git a/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php
index 4b92b8d6d25f4e4217e09f0dea4bb730d716f8fd..2bd30610cb19a316bca669549752f2fd61397bc0 100644
--- a/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php
+++ b/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php
@@ -6,7 +6,6 @@
 
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Extension\ProceduralCall;
-use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\Core\Hook\HookCollectorPass;
 use Drupal\Tests\UnitTestCase;
 use Drupal\Tests\Core\GroupIncludesTestTrait;
@@ -23,8 +22,8 @@ class HookCollectorPassTest extends UnitTestCase {
   use GroupIncludesTestTrait;
 
   /**
-   * @covers ::collectAllHookImplementations
-   * @covers ::filterIterator
+   * @covers \Drupal\Core\Hook\HookCollector::collectAllHookImplementations
+   * @covers \Drupal\Core\Hook\HookCollector::filterIterator
    */
   public function testCollectAllHookImplementations(): void {
     vfsStream::setup('drupal_root');
@@ -73,7 +72,7 @@ function test_module_should_be_skipped();
 
   /**
    * @covers ::process
-   * @covers ::collectModuleHookImplementations
+   * @covers \Drupal\Core\Hook\HookCollector::collectModuleHookImplementations
    */
   public function testGroupIncludes(): void {
     $module_filenames = self::setupGroupIncludes();
@@ -85,35 +84,4 @@ public function testGroupIncludes(): void {
     $this->assertSame(self::GROUP_INCLUDES, $argument);
   }
 
-  /**
-   * @covers ::getHookAttributesInClass
-   */
-  public function testGetHookAttributesInClass(): void {
-    // @phpstan-ignore-next-line
-    $getHookAttributesInClass = fn ($class) => $this->getHookAttributesInClass($class);
-    $p = new HookCollectorPass();
-    $getHookAttributesInClass = $getHookAttributesInClass->bindTo($p, $p);
-
-    $x = new class {
-
-      #[Hook('foo')]
-      function foo(): void {}
-
-    };
-    $hooks = $getHookAttributesInClass(get_class($x));
-    $hook = reset($hooks);
-    $this->assertInstanceOf(Hook::class, $hook);
-    $this->assertSame('foo', $hook->hook);
-
-    $x = new class {
-
-      #[Hook('install')]
-      function foo(): void {}
-
-    };
-    $this->expectException(\LogicException::class);
-    // This will throw exception, and stop code execution.
-    $getHookAttributesInClass(get_class($x));
-  }
-
 }