diff --git a/core/core.api.php b/core/core.api.php
index 0500a3c58aecebc87336db728c2818d51135bc86..e93fa0bb5b5c9c168a55ee726c3415e17b6bf08e 100644
--- a/core/core.api.php
+++ b/core/core.api.php
@@ -1606,6 +1606,14 @@
  * modules that they interact with. Your modules can also define their own
  * hooks, in order to let other modules interact with them.
  *
+ * Hook implementations will execute in the following order.
+ * order.
+ * - Module weight.
+ * - Alphabetical by module name.
+ * - This order can be modified by using the order parameter on the #[Hook]
+ *   attribute, using the #[ReorderHook] attribute, or implementing the legacy
+ *   hook_module_implements_alter.
+ *
  * @section implementing Implementing a hook
  *
  * There are two ways to implement a hook:
@@ -1657,6 +1665,7 @@
  * Legacy meta hooks:
  * - hook_hook_info()
  * - hook_module_implements_alter()
+ * @see \Drupal\Core\Hook\Attribute\LegacyModuleImplementsAlter
  *
  * Install hooks:
  * - hook_install()
@@ -1709,6 +1718,90 @@
  * @see \Drupal\Core\Hook\Attribute\Hook
  * @see \Drupal::moduleHandler()
  *
+ * @section ordering_hooks Ordering hook implementations
+ *
+ * The order in which hook implementations are executed can be modified. A hook
+ * can be placed first or last in the order of execution. It can also be placed
+ * before or after the execution of another module's implementation of the same
+ * hook. When changing the order of execution in relation to a specific module
+ * either the module name or the class and method can be used.
+ *
+ * Use the order argument of the Hook attribute to order the execution of
+ * hooks.
+ *
+ * Example of executing 'entity_type_alter' of my_module first:
+ * @code
+ * #[Hook('entity_type_alter', order: Order::First)]
+ * @endcode
+ *
+ * Example of executing 'entity_type_alter' of my_module last:
+ * @code
+ * #[Hook('entity_type_alter', order: Order::Last)]
+ * @endcode
+ *
+ * Example of executing 'entity_type_alter' before the execution of the
+ * implementation in the foo module:
+ * @code
+ * #[Hook('entity_type_alter', order: new OrderBefore(['foo']))]
+ * @endcode
+ *
+ * Example of executing 'entity_type_alter' after the execution of the
+ * implementation in the foo module:
+ * @code
+ * #[Hook('entity_type_alter', order: new OrderAfter(['foo']))]
+ * @endcode
+ *
+ * Example of executing 'entity_type_alter' before two methods. One in the Foo
+ * class and one in the Bar class.
+ * @code
+ * #[Hook('entity_type_alter',
+ *   order: new OrderBefore(
+ *     classesAndMethods: [
+ *       [Foo::class, 'someMethod'],
+ *       [Bar::class, 'someOtherMethod'],
+ *     ]
+ *   )
+ * )]
+ * @endcode
+ *
+ * @see \Drupal\Core\Hook\Attribute\Hook
+ * @see \Drupal\Core\Hook\Order\Order
+ * @see \Drupal\Core\Hook\Order\OrderBefore
+ * @see \Drupal\Core\Hook\Order\OrderAfter
+ *
+ * @section ordering_other_module_hooks Ordering other module hook implementations
+ *
+ * The order in which hooks implemented in other modules are executed can be
+ * reordered. The reordering of the targeted hook is done relative to other
+ * implementations. The reordering process executes after the ordering defined
+ * in the Hook attribute.
+ *
+ * Example of reordering the execution of the 'entity_presave' hook so that
+ * Content Moderation module hook executes before the Workspaces module hook.
+ * @code
+ * #[ReorderHook('entity_presave',
+ *   class: ContentModerationHooks::class,
+ *   method: 'entityPresave',
+ *   order: new OrderBefore(['workspaces'])
+ * )]
+ * @endcode
+ *
+ * @see \Drupal\Core\Hook\Attribute\ReorderHook
+ *
+ * @section removing_hooks Removing hook implementations
+ *
+ * The execution of a hooks implemented by other modules can be skipped. This
+ * is done by removing the targeted hook, use the RemoveHook attribute.
+ *
+ * Example of removing the 'help' hook of the Layout Builder module.
+ * @code
+ * #[RemoveHook('help',
+ *   class: LayoutBuilderHooks::class,
+ *   method: 'help'
+ * )]
+ * @endcode
+ *
+ * @see \Drupal\Core\Hook\Attribute\RemoveHook
  * @}
  */
 
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 1f9ff1cb0f63489545ab8fd0ebdccfb074426b04..53cf3c95aa5f5cfe1ec0d9899f0cdf1dc19f306d 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -3,10 +3,12 @@
 namespace Drupal\Core\Extension;
 
 use Drupal\Component\Graph\Graph;
+use Drupal\Component\Render\FormattableMarkup;
 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\OrderOperation\OrderOperation;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 
 /**
@@ -58,33 +60,66 @@ class ModuleHandler implements ModuleHandlerInterface {
    */
   protected $includeFileKeys = [];
 
+  /**
+   * Lists of implementation callables by hook.
+   *
+   * @var array<string, list<callable>>
+   */
+  protected array $listenersByHook = [];
+
+  /**
+   * Lists of module names by hook.
+   *
+   * The indices are exactly the same as in $listenersByHook.
+   *
+   * @var array<string, list<string>>
+   */
+  protected array $modulesByHook = [];
+
   /**
    * Hook and module keyed list of listeners.
    *
-   * @var array
+   * @var array<string, array<string, list<callable>>>
    */
   protected array $invokeMap = [];
 
+  /**
+   * Ordering rules by hook name.
+   *
+   * @var array<string, list<\Drupal\Core\Hook\OrderOperation\OrderOperation>>
+   */
+  protected array $orderingRules = [];
+
   /**
    * Constructs a ModuleHandler object.
    *
    * @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>> $packedOrderOperations
+   *   Ordering rules by hook name, serialized.
    *
    * @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 $packedOrderOperations = [],
+  ) {
     $this->root = $root;
     $this->moduleList = [];
     foreach ($module_list as $name => $module) {
@@ -211,6 +246,8 @@ protected function add($type, $name, $path) {
     foreach ($hook_collector->getImplementations() as $hook => $moduleImplements) {
       foreach ($moduleImplements as $module => $classImplements) {
         foreach ($classImplements[ProceduralCall::class] ?? [] as $method) {
+          $this->listenersByHook[$hook][] = $method;
+          $this->modulesByHook[$hook][] = $module;
           $this->invokeMap[$hook][$module][] = $method;
         }
       }
@@ -310,10 +347,9 @@ public function hasImplementations(string $hook, $modules = NULL): bool {
    * {@inheritdoc}
    */
   public function invokeAllWith(string $hook, callable $callback): void {
-    foreach ($this->getHookListeners($hook) as $module => $listeners) {
-      foreach ($listeners as $listener) {
-        $callback($listener, $module);
-      }
+    foreach ($this->getFlatHookListeners($hook) as $index => $listener) {
+      $module = $this->modulesByHook[$hook][$index];
+      $callback($listener, $module);
     }
   }
 
@@ -415,14 +451,6 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
     // specific variants of it, as in the case of ['form', 'form_FORM_ID'].
     if (is_array($type)) {
       $cid = implode(',', $type);
-      $extra_types = $type;
-      $type = array_shift($extra_types);
-      // Allow if statements in this function to use the faster isset() rather
-      // than !empty() both when $type is passed as a string, or as an array
-      // with one item.
-      if (empty($extra_types)) {
-        unset($extra_types);
-      }
     }
     else {
       $cid = $type;
@@ -432,40 +460,160 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
     // list of functions to call, and on subsequent calls, iterate through them
     // quickly.
     if (!isset($this->alterEventListeners[$cid])) {
-      $this->alterEventListeners[$cid] = [];
-      $hook = $type . '_alter';
-      $hook_listeners = $this->getHookListeners($hook);
-      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;
-            }
-          }
+      $hooks = is_array($type)
+        ? array_map(static fn (string $type) => $type . '_alter', $type)
+        : [$type . '_alter'];
+      $this->alterEventListeners[$cid] = $this->getCombinedListeners($hooks);
+    }
+    foreach ($this->alterEventListeners[$cid] as $listener) {
+      $listener($data, $context1, $context2);
+    }
+  }
+
+  /**
+   * Builds a list of listeners for an alter hook.
+   *
+   * @param list<string> $hooks
+   *   The hooks passed to the ->alter() call.
+   *
+   * @return list<callable>
+   *   List of implementation callables.
+   */
+  protected function getCombinedListeners(array $hooks): array {
+    // Get implementation lists for each hook.
+    $listener_lists = array_map($this->getFlatHookListeners(...), $hooks);
+    // Remove empty lists.
+    $listener_lists = array_filter($listener_lists);
+    if (!$listener_lists) {
+      // No implementations exist.
+      return [];
+    }
+    if (array_keys($listener_lists) === [0]) {
+      // Only the first hook has implementations.
+      return $listener_lists[0];
+    }
+    // Collect the lists from each hook and group the listeners by module.
+    $listeners_by_identifier = [];
+    $modules_by_identifier = [];
+    $identifiers_by_module = [];
+    foreach ($listener_lists as $i_hook => $listeners) {
+      $hook = $hooks[$i_hook];
+      foreach ($listeners as $i_listener => $listener) {
+        $module = $this->modulesByHook[$hook][$i_listener];
+        $identifier = is_array($listener)
+          ? get_class($listener[0]) . '::' . $listener[1]
+          : ProceduralCall::class . '::' . $listener;
+        $other_module = $modules_by_identifier[$identifier] ?? NULL;
+        if ($other_module !== NULL) {
+          $this->triggerErrorForDuplicateAlterHookListener(
+            $hooks,
+            $module,
+            $other_module,
+            $listener,
+            $identifier,
+          );
+          // Don't add the same listener more than once.
+          continue;
         }
+        $listeners_by_identifier[$identifier] = $listener;
+        $modules_by_identifier[$identifier] = $module;
+        $identifiers_by_module[$module][] = $identifier;
       }
-      // 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);
-      }
-      foreach ($modules as $module) {
-        foreach ($hook_listeners[$module] ?? [] as $listener) {
-          $this->alterEventListeners[$cid][] = $listener;
-        }
+    }
+    // First we get the the modules in moduleList order, this order is module
+    // weight then alphabetical. Then we apply legacy ordering using
+    // hook_module_implements_alter(). Finally we order using order attributes.
+    $modules = array_keys($identifiers_by_module);
+    $modules = $this->reOrderModulesForAlter($modules, $hooks[0]);
+    // Create a flat list of identifiers, using the new module order.
+    $identifiers = array_merge(...array_map(
+      fn (string $module) => $identifiers_by_module[$module],
+      $modules,
+    ));
+    foreach ($hooks as $hook) {
+      foreach ($this->getHookOrderingRules($hook) as $rule) {
+        $rule->apply($identifiers, $modules_by_identifier);
+        // Order operations must not:
+        // - Insert duplicate keys.
+        // - Change the array to be not a list.
+        // - Add or remove values.
+        assert($identifiers === array_unique($identifiers));
+        assert(array_is_list($identifiers));
+        assert(!array_diff($identifiers, array_keys($modules_by_identifier)));
+        assert(!array_diff(array_keys($modules_by_identifier), $identifiers));
       }
     }
-    foreach ($this->alterEventListeners[$cid] as $listener) {
-      $listener($data, $context1, $context2);
+    return array_map(
+      static fn (string $identifier) => $listeners_by_identifier[$identifier],
+      $identifiers,
+    );
+  }
+
+  /**
+   * Triggers an error on duplicate alter listeners.
+   *
+   * This is called when the same method is registered for multiple hooks, which
+   * are now part of the same alter call.
+   *
+   * @param list<string> $hooks
+   *   Hook names from the ->alter() call.
+   * @param string $module
+   *   The module name for one of the hook implementations.
+   * @param string $other_module
+   *   The module name for another hook implementation.
+   * @param callable $listener
+   *   The hook listener.
+   * @param string $identifier
+   *   String identifier of the hook listener.
+   */
+  protected function triggerErrorForDuplicateAlterHookListener(array $hooks, string $module, string $other_module, callable $listener, string $identifier): void {
+    $log_message_replacements = [
+      '@implementation' => is_array($listener)
+        ? ('method ' . $identifier . '()')
+        : ('function ' . $listener[1] . '()'),
+      '@hooks' => "['" . implode("', '", $hooks) . "']",
+    ];
+    if ($other_module !== $module) {
+      // There is conflicting information about which module this
+      // implementation is registered for. At this point we cannot even
+      // be sure if the module is the one from the main hook or the extra
+      // hook. This means that ordering may not work as expected and it is
+      // unclear if the intention is to execute the code multiple times. This
+      // can be resolved by using a separate method for alter hooks that
+      // implement on behalf of other modules.
+      trigger_error((string) new FormattableMarkup(
+        'The @implementation is registered for more than one of the alter hooks @hooks from the current ->alter() call, on behalf of different modules @module and @other_module. Only one instance will be part of the implementation list for this hook combination. For the purpose of ordering, the module @module will be used.',
+        [
+          ...$log_message_replacements,
+          '@module' => "'$module'",
+          '@other_module' => "'$other_module'",
+        ],
+      ), E_USER_WARNING);
     }
+    else {
+      // There is no conflict, but probably one or more redundant #[Hook]
+      // attributes should be removed.
+      trigger_error((string) new FormattableMarkup(
+        'The @implementation is registered for more than one of the alter hooks @hooks from the current ->alter() call. Only one instance will be part of the implementation list for this hook combination.',
+        $log_message_replacements,
+      ), E_USER_NOTICE);
+    }
+  }
+
+  /**
+   * Gets ordering rules for a hook.
+   *
+   * @param string $hook
+   *   Hook name.
+   *
+   * @return list<\Drupal\Core\Hook\OrderOperation\OrderOperation>
+   *   List of order operations for the hook.
+   */
+  protected function getHookOrderingRules(string $hook): array {
+    return $this->orderingRules[$hook] ??= array_map(
+      OrderOperation::unpack(...),
+      $this->packedOrderOperations[$hook] ?? [],
+    );
   }
 
   /**
@@ -549,14 +697,39 @@ public function writeCache() {
   }
 
   /**
+   * Gets hook listeners by module.
+   *
    * @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 {
     if (!isset($this->invokeMap[$hook])) {
+      $this->invokeMap[$hook] = [];
+      foreach ($this->getFlatHookListeners($hook) as $index => $listener) {
+        $module = $this->modulesByHook[$hook][$index];
+        $this->invokeMap[$hook][$module][] = $listener;
+      }
+    }
+
+    return $this->invokeMap[$hook] ?? [];
+  }
+
+  /**
+   * Gets a list of hook listener callbacks.
+   *
+   * @param string $hook
+   *   The hook name.
+   *
+   * @return list<callable>
+   *   A list of hook implementation callables.
+   *
+   * @internal
+   */
+  protected function getFlatHookListeners(string $hook): array {
+    if (!isset($this->listenersByHook[$hook])) {
       foreach ($this->eventDispatcher->getListeners("drupal_hook.$hook") as $listener) {
         if (is_array($listener) && is_object($listener[0])) {
           $module = $this->hookImplementationsMap[$hook][get_class($listener[0])][$listener[1]];
@@ -569,7 +742,8 @@ protected function getHookListeners(string $hook): array {
             $callable = $listener;
           }
           if (isset($this->moduleList[$module])) {
-            $this->invokeMap[$hook][$module][] = $callable;
+            $this->listenersByHook[$hook][] = $callable;
+            $this->modulesByHook[$hook][] = $module;
           }
         }
       }
@@ -580,7 +754,8 @@ protected function getHookListeners(string $hook): array {
         }
       }
     }
-    return $this->invokeMap[$hook] ?? [];
+
+    return $this->listenersByHook[$hook] ?? [];
   }
 
 }
diff --git a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php
index 597da739a14658bfa54167701467e1baf3ebaf64..529fd7275a81f54a45b457b33d4c23a3d8ed5821 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php
@@ -225,7 +225,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 325335615abbb60bfd50ffab6cb817132e92797d..a5f160a8f92ae61afc9fee9c9d9c20ffdb440765 100644
--- a/core/lib/Drupal/Core/Extension/module.api.php
+++ b/core/lib/Drupal/Core/Extension/module.api.php
@@ -94,6 +94,10 @@ function hook_hook_info(): array {
 /**
  * Alter the registry of modules implementing a hook.
  *
+ * This hook will be removed in 12.0.0. It is not deprecated in order to
+ * support the "#[LegacyModuleImplementsAlter]" attribute, used prior to Drupal
+ * 11.2.0.
+ *
  * Only procedural implementations are supported for this hook.
  *
  * This hook is invoked in \Drupal::moduleHandler()->getImplementationInfo().
@@ -115,6 +119,8 @@ function hook_hook_info(): array {
  *   file named $module.$group.inc.
  * @param string $hook
  *   The name of the module hook being implemented.
+ *
+ * @see \Drupal\Core\Hook\Attribute\LegacyModuleImplementsAlter
  */
 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..33da9558b512738519c2aa047a9d96af5a248aac 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -4,6 +4,8 @@
 
 namespace Drupal\Core\Hook\Attribute;
 
+use Drupal\Core\Hook\Order\OrderInterface;
+
 /**
  * Attribute for defining a class method as a hook implementation.
  *
@@ -30,8 +32,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 Drupal\Core\Hook\Order\OrderInterface for more information.
+ *
+ * Removing hook implementations can be done by using the attribute
+ * \Drupal\Core\Hook\Attribute\RemoveHook.
+ *
+ * Ordering hook implementations in other modules can be done by using the
+ * attribute \Drupal\Core\Hook\Attribute\ReorderHook.
  *
  * Classes that use this annotation on the class or on their methods are
  * automatically registered as autowired services with the class name as the
@@ -88,7 +96,7 @@
  * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
-class Hook {
+class Hook implements HookAttributeInterface {
 
   /**
    * Constructs a Hook attribute object.
@@ -104,23 +112,14 @@ 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\OrderInterface|null $order
+   *   (optional) Set the order of the implementation.
    */
   public function __construct(
     public string $hook,
     public string $method = '',
     public ?string $module = NULL,
+    public OrderInterface|null $order = NULL,
   ) {}
 
-  /**
-   * Set the method the hook should apply to.
-   *
-   * @param string $method
-   *   The method that the hook attribute applies to.
-   *   This only needs to be set when the attribute is on the class.
-   */
-  public function setMethod(string $method): static {
-    $this->method = $method;
-    return $this;
-  }
-
 }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookAttributeInterface.php b/core/lib/Drupal/Core/Hook/Attribute/HookAttributeInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..8a2f2413b20fe8553a21381cc36b2588d43ab72f
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookAttributeInterface.php
@@ -0,0 +1,15 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Attribute;
+
+/**
+ * Common interface for attributes used for hook discovery.
+ *
+ * This does not imply any shared behavior, it is only used to collect all
+ * hook-related attributes in the same call.
+ *
+ * @internal
+ */
+interface HookAttributeInterface {}
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..30929be33bc43c575e630b9ba9f37635e78ef9bd
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Attribute/LegacyModuleImplementsAlter.php
@@ -0,0 +1,22 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Attribute;
+
+/**
+ * Prevents procedural hook_module_implements_alter from executing.
+ *
+ * This allows the use of the legacy hook_module_implements_alter alongside the
+ * new attribute-based ordering.Providing support for versions of Drupal older
+ * than 11.2.0.
+ *
+ * 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.
+ */
+#[\Attribute(\Attribute::TARGET_FUNCTION)]
+class LegacyModuleImplementsAlter {}
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..28d4c9456cae920d660cfddb65f0a5b3573706d5
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Attribute;
+
+/**
+ * Removes an already existing implementation.
+ *
+ * The effect of this attribute is independent from the specific class or method
+ * on which it is placed.
+ */
+#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class RemoveHook implements HookAttributeInterface {
+
+  /**
+   * Constructs a RemoveHook object.
+   *
+   * @param string $hook
+   *   The hook name from which to remove the target implementation.
+   * @param class-string $class
+   *   The class name of the target hook implementation.
+   * @param string $method
+   *   The method name of the target hook implementation.
+   *   If the class instance itself is the listener, this should be '__invoke'.
+   */
+  public function __construct(
+    public readonly string $hook,
+    public readonly string $class,
+    public readonly string $method,
+  ) {}
+
+}
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..920552ec448e2dffa2b209dd4aee8bf5f9447331
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Attribute/ReorderHook.php
@@ -0,0 +1,40 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Attribute;
+
+use Drupal\Core\Hook\Order\OrderInterface;
+
+/**
+ * Sets the order of an already existing implementation.
+ *
+ * The effect of this attribute is independent from the specific class or method
+ * on which it is placed.
+ */
+#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class ReorderHook implements HookAttributeInterface {
+
+  /**
+   * Constructs a ReorderHook object.
+   *
+   * @param string $hook
+   *   The hook for which to reorder an implementation.
+   * @param class-string $class
+   *   The class of the targeted hook implementation.
+   * @param string $method
+   *   The method name of the targeted hook implementation.
+   *   If the #[Hook] attribute is on the class itself, this should be
+   *   '__invoke'.
+   * @param \Drupal\Core\Hook\Order\OrderInterface $order
+   *   Specifies a new position for the targeted hook implementation relative to
+   *   other implementations.
+   */
+  public function __construct(
+    public string $hook,
+    public string $class,
+    public string $method,
+    public OrderInterface $order,
+  ) {}
+
+}
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 3809e24af21d56b20f034b2a45848de6d47e6e93..df2f9f39cfa65249980b627f8c937ccce4b33068 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -9,8 +9,13 @@
 use Drupal\Component\FileCache\FileCacheFactory;
 use Drupal\Core\Extension\ProceduralCall;
 use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Attribute\HookAttributeInterface;
 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 Drupal\Core\Hook\OrderOperation\OrderOperation;
 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 
@@ -26,29 +31,50 @@
  *
  * Finally, a hook_implementations_map container parameter is added. This
  * contains a mapping from [hook,class,method] to the module name.
+ *
+ * @internal
  */
 class HookCollectorPass implements CompilerPassInterface {
 
   /**
-   * An associative array of hook implementations.
+   * OOP implementation module names keyed by hook name and "$class::$method".
+   *
+   * @var array<string, array<string, string>>
+   */
+  protected array $oopImplementations = [];
+
+  /**
+   * Procedural implementation module names by hook name.
    *
-   * Keys are hook, module, class. Values are a list of methods.
+   * @var array<string, list<string>>
    */
-  protected array $implementations = [];
+  protected array $proceduralImplementations = [];
 
   /**
-   * An associative array of hook implementations.
+   * Order operations grouped by hook name and weight.
    *
-   * Keys are hook, module and an empty string value.
+   * Operations with higher weight are applied last, which means they can
+   * override the changes from previous operations.
    *
-   * @see hook_module_implements_alter()
+   * @var array<string, array<int, list<\Drupal\Core\Hook\OrderOperation\OrderOperation>>>
+   *
+   * @todo Review how to combine operations from different hooks.
    */
-  protected array $moduleImplements = [];
+  protected array $orderOperations = [];
 
   /**
-   * A list of include files.
+   * Identifiers to remove, as "$class::$method", keyed by hook name.
+   *
+   * @var array<string, list<string>>
+   */
+  protected array $removeHookIdentifiers = [];
+
+  /**
+   * A map of include files by function name.
    *
    * (This is required only for BC.)
+   *
+   * @var array<string, string>
    */
   protected array $includes = [];
 
@@ -56,6 +82,8 @@ class HookCollectorPass implements CompilerPassInterface {
    * A list of functions implementing hook_module_implements_alter().
    *
    * (This is required only for BC.)
+   *
+   * @var list<callable-string>
    */
   protected array $moduleImplementsAlters = [];
 
@@ -63,69 +91,248 @@ class HookCollectorPass implements CompilerPassInterface {
    * A list of functions implementing hook_hook_info().
    *
    * (This is required only for BC.)
+   *
+   * @var list<callable-string>
    */
   private array $hookInfo = [];
 
   /**
-   * A list of .inc files.
+   * Include files, keyed by the $group part of "/$module.$group.inc".
+   *
+   * @var array<string, list<string>>
    */
   private array $groupIncludes = [];
 
+  /**
+   * Constructor.
+   *
+   * @param list<string> $modules
+   *   Names of installed modules.
+   *   When used as a compiler pass, this parameter should be omitted.
+   */
+  public function __construct(
+    protected readonly array $modules = [],
+  ) {}
+
   /**
    * {@inheritdoc}
    */
   public function process(ContainerBuilder $container): void {
-    $collector = static::collectAllHookImplementations($container->getParameter('container.modules'), $container);
-    $map = [];
+    $module_list = $container->getParameter('container.modules');
+    $parameters = $container->getParameterBag()->all();
+    $skip_procedural_modules = array_filter(
+      array_keys($module_list),
+      static fn (string $module) => !empty($parameters["$module.hooks_converted"]),
+    );
+    $collector = HookCollectorPass::collectAllHookImplementations($module_list, $skip_procedural_modules);
+
+    $collector->writeToContainer($container);
+  }
+
+  /**
+   * Writes collected definitions to the container builder.
+   *
+   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
+   *   Container builder.
+   */
+  public function writeToContainer(ContainerBuilder $container): void {
     $container->register(ProceduralCall::class, ProceduralCall::class)
-      ->addArgument($collector->includes);
+      ->addArgument($this->includes);
+
+    // Gather includes for each hook_hook_info group. Store this in
+    // $groupIncludes so the module handler includes the files at runtime when
+    // the hooks are invoked.
     $groupIncludes = [];
-    foreach ($collector->hookInfo as $function) {
+    foreach ($this->hookInfo as $function) {
       foreach ($function() as $hook => $info) {
-        if (isset($collector->groupIncludes[$info['group']])) {
-          $groupIncludes[$hook] = $collector->groupIncludes[$info['group']];
+        if (isset($this->groupIncludes[$info['group']])) {
+          $groupIncludes[$hook] = $this->groupIncludes[$info['group']];
         }
       }
     }
+
+    $implementationsByHook = $this->calculateImplementations();
+
+    static::writeImplementationsToContainer($container, $implementationsByHook);
+
+    // Update the module handler definition.
     $definition = $container->getDefinition('module_handler');
     $definition->setArgument('$groupIncludes', $groupIncludes);
-    foreach ($collector->moduleImplements as $hook => $moduleImplements) {
-      foreach ($collector->moduleImplementsAlters as $alter) {
+
+    $packed_order_operations = [];
+    $order_operations = $this->getOrderOperations();
+    foreach (preg_grep('@_alter$@', array_keys($order_operations)) as $alter_hook) {
+      $packed_order_operations[$alter_hook] = array_map(
+        fn (OrderOperation $operation) => $operation->pack(),
+        $order_operations[$alter_hook],
+      );
+    }
+    $definition->setArgument('$packedOrderOperations', $packed_order_operations);
+  }
+
+  /**
+   * 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 = [];
+    foreach ($this->proceduralImplementations as $hook => $procedural_modules) {
+      foreach ($procedural_modules as $module) {
+        $implementationsByHook[$hook][ProceduralCall::class . '::' . $module . '_' . $hook] = $module;
+      }
+    }
+    foreach ($this->oopImplementations as $hook => $oopImplementations) {
+      if (!isset($implementationsByHook[$hook])) {
+        $implementationsByHook[$hook] = $oopImplementations;
+      }
+      else {
+        $implementationsByHook[$hook] += $oopImplementations;
+      }
+    }
+    foreach ($this->removeHookIdentifiers as $hook => $identifiers_to_remove) {
+      foreach ($identifiers_to_remove as $identifier_to_remove) {
+        unset($implementationsByHook[$hook][$identifier_to_remove]);
+      }
+      if (empty($implementationsByHook[$hook])) {
+        unset($implementationsByHook[$hook]);
+      }
+    }
+    return $implementationsByHook;
+  }
+
+  /**
+   * Calculates the ordered implementations.
+   *
+   * @return array<string, array<string, string>>
+   *   Implementations, as module names keyed by hook name and "$class::$method"
+   *   identifier.
+   */
+  protected function calculateImplementations(): array {
+    $implementationsByHookOrig = $this->getFilteredImplementations();
+
+    // List of hooks and modules formatted for hook_module_implements_alter().
+    $moduleImplementsMap = [];
+    foreach ($implementationsByHookOrig as $hook => $hookImplementations) {
+      foreach (array_intersect($this->modules, $hookImplementations) as $module) {
+        $moduleImplementsMap[$hook][$module] = '';
+      }
+    }
+
+    $implementationsByHook = [];
+    foreach ($moduleImplementsMap as $hook => $moduleImplements) {
+      // Process all hook_module_implements_alter() for build time ordering.
+      foreach ($this->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--,
-            ]);
-          }
+        foreach (array_keys($implementationsByHookOrig[$hook], $module, TRUE) as $identifier) {
+          $implementationsByHook[$hook][$identifier] = $module;
         }
       }
     }
+
+    foreach ($this->getOrderOperations() as $hook => $order_operations) {
+      self::applyOrderOperations($implementationsByHook[$hook], $order_operations);
+    }
+
+    return $implementationsByHook;
+  }
+
+  /**
+   * Gets order operations by hook.
+   *
+   * @return array<string, list<\Drupal\Core\Hook\OrderOperation\OrderOperation>>
+   *   Order operations by hook name.
+   */
+  protected function getOrderOperations(): array {
+    $operations_by_hook = [];
+    foreach ($this->orderOperations as $hook => $order_operations_by_weight) {
+      ksort($order_operations_by_weight);
+      $operations_by_hook[$hook] = array_merge(...$order_operations_by_weight);
+    }
+    return $operations_by_hook;
+  }
+
+  /**
+   * Applies order operations to a hook implementation list.
+   *
+   * @param array<string, string> $implementation_list
+   *   Implementation list for one hook, as module names keyed by
+   *   "$class::$method" identifiers.
+   * @param list<\Drupal\Core\Hook\OrderOperation\OrderOperation> $order_operations
+   *   A list of order operations for one hook.
+   */
+  public static function applyOrderOperations(array &$implementation_list, array $order_operations): void {
+    $module_finder = $implementation_list;
+    $identifiers = array_keys($module_finder);
+    foreach ($order_operations as $order_operation) {
+      $order_operation->apply($identifiers, $module_finder);
+      assert($identifiers === array_unique($identifiers));
+      $identifiers = array_values($identifiers);
+    }
+    // Clean up after bad order operations.
+    $identifiers = array_combine($identifiers, $identifiers);
+    $identifiers = array_intersect_key($identifiers, $module_finder);
+    $implementation_list = array_replace($identifiers, $module_finder);
+  }
+
+  /**
+   * 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 $module_filenames
+   * @param array<string, array{pathname: string}> $module_list
    *   An associative array. Keys are the module names, values are relevant
    *   info yml file path.
-   * @param Symfony\Component\DependencyInjection\ContainerBuilder|null $container
-   *   The container.
+   * @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
@@ -134,20 +341,21 @@ public function process(ContainerBuilder $container): void {
    * @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
+   * @todo Pass only $container when ModuleHandler::add() is removed in Drupal
+   * 12.0.0.
    */
-  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");
-      }
+  public static function collectAllHookImplementations(array $module_list, array $skipProceduralModules = []): static {
+    $modules = array_keys($module_list);
+    $modules_by_length = $modules;
+    usort($modules_by_length, static fn ($a, $b) => strlen($b) - strlen($a));
+    $known_modules_pattern = implode('|', array_map(
+      static fn ($x) => preg_quote($x, '/'),
+      $modules_by_length,
+    ));
+    $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;
@@ -195,16 +403,33 @@ protected function collectModuleHookImplementations($dir, $module, $module_preg,
           $namespace = preg_replace('#^src/#', "Drupal/$module/", $iterator->getSubPath());
           $class = $namespace . '/' . $fileinfo->getBasename('.php');
           $class = str_replace('/', '\\', $class);
+          $attributes = [];
           if (class_exists($class)) {
-            $attributes = static::getHookAttributesInClass($class);
+            $reflectionClass = new \ReflectionClass($class);
+            $attributes = self::getAttributeInstances($reflectionClass);
             $hook_file_cache->set($filename, ['class' => $class, 'attributes' => $attributes]);
           }
-          else {
-            $attributes = [];
-          }
         }
-        foreach ($attributes as $attribute) {
-          $this->addFromAttribute($attribute, $class, $module);
+        foreach ($attributes as $method => $methodAttributes) {
+          foreach ($methodAttributes as $attribute) {
+            if ($attribute instanceof Hook) {
+              self::checkForProceduralOnlyHooks($attribute, $class);
+              $this->oopImplementations[$attribute->hook][$class . '::' . ($attribute->method ?: $method)] = $attribute->module ?? $module;
+              if ($attribute->order !== NULL) {
+                // Use a lower weight for order operations that are declared
+                // together with the hook listener they apply to.
+                $this->orderOperations[$attribute->hook][0][] = $attribute->order->getOperation("$class::$method");
+              }
+            }
+            elseif ($attribute instanceof ReorderHook) {
+              // Use a higher weight for order operations that target other hook
+              // listeners.
+              $this->orderOperations[$attribute->hook][1][] = $attribute->order->getOperation($attribute->class . '::' . $attribute->method);
+            }
+            elseif ($attribute instanceof RemoveHook) {
+              $this->removeHookIdentifiers[$attribute->hook][] = $attribute->class . '::' . $attribute->method;
+            }
+          }
         }
       }
       elseif (!$skip_procedural) {
@@ -217,14 +442,15 @@ protected function collectModuleHookImplementations($dir, $module, $module_preg,
             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']];
+            if (!StaticReflectionParser::hasAttribute($attributes, LegacyHook::class) && preg_match($module_preg, $function, $matches) && !StaticReflectionParser::hasAttribute($attributes, LegacyModuleImplementsAlter::class)) {
+              assert($function === $matches['module'] . '_' . $matches['hook']);
+              $implementations[] = ['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']);
+          $this->addProceduralImplementation($fileinfo, $implementation['hook'], $implementation['module']);
         }
       }
       if ($extension === 'inc') {
@@ -250,89 +476,33 @@ protected static function filterIterator(\SplFileInfo $fileInfo, $key, \Recursiv
         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($fileInfo->getFilename(), ['tests', 'js', 'css']) && !array_filter(scandir($key), static 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)
+   *   The file this procedural implementation is in.
    * @param string $hook
-   *   The name of the hook. (Huh, right?)
+   *   The name of the hook.
    * @param string $module
-   *   The name of the module. (Truly shocking!)
-   * @param string $function
-   *   The name of function implementing the hook. (Wow!)
+   *   The module implementing the hook, or on behalf of which the hook is
+   *   implemented.
    */
-  protected function addProceduralImplementation(\SplFileInfo $fileinfo, string $hook, string $module, string $function): void {
-    $this->addFromAttribute(new Hook($hook, $module . '_' . $hook), ProceduralCall::class, $module);
+  protected function addProceduralImplementation(\SplFileInfo $fileinfo, string $hook, string $module): void {
+    $function = $module . '_' . $hook;
     if ($hook === 'hook_info') {
       $this->hookInfo[] = $function;
     }
-    if ($hook === 'module_implements_alter') {
+    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->proceduralImplementations[$hook][] = $module;
     if ($fileinfo->getExtension() !== 'module') {
       $this->includes[$function] = $fileinfo->getPathname();
     }
@@ -341,6 +511,8 @@ protected function addProceduralImplementation(\SplFileInfo $fileinfo, string $h
   /**
    * This method is only to be used by ModuleHandler.
    *
+   * @todo Remove when ModuleHandler::add() is removed in Drupal 12.0.0.
+   *
    * @internal
    */
   public function loadAllIncludes(): void {
@@ -352,21 +524,40 @@ public function loadAllIncludes(): void {
   /**
    * 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 in Drupal 12.0.0.
+   *
    * @internal
    */
   public function getImplementations(): array {
-    return $this->implementations;
+    $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 $hook
+   * @param \Drupal\Core\Hook\Attribute\Hook $hookAttribute
    *   The hook to check.
-   * @param string $class
+   * @param class-string $class
    *   The class the hook is implemented on.
    */
-  public static function checkForProceduralOnlyHooks(Hook $hook, string $class): void {
+  public static function checkForProceduralOnlyHooks(Hook $hookAttribute, string $class): void {
     $staticDenyHooks = [
       'hook_info',
       'install',
@@ -379,9 +570,31 @@ public static function checkForProceduralOnlyHooks(Hook $hook, string $class): v
       '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.");
+    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\Attribute\HookAttributeInterface>>
+   *   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(HookAttributeInterface::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/Order/Order.php b/core/lib/Drupal/Core/Hook/Order/Order.php
new file mode 100644
index 0000000000000000000000000000000000000000..6a7934df7d2a02fae9c830856a2af5270b6505d1
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Order/Order.php
@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Order;
+
+use Drupal\Core\Hook\OrderOperation\FirstOrLast;
+use Drupal\Core\Hook\OrderOperation\OrderOperation;
+
+/**
+ * Set this implementation to be first or last.
+ */
+enum Order: int implements OrderInterface {
+
+  // This implementation should execute first.
+  case First = 1;
+
+  // This implementation should execute last.
+  case Last = 0;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOperation(string $identifier): OrderOperation {
+    return new FirstOrLast($identifier, $this === self::Last);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Hook/Order/OrderAfter.php b/core/lib/Drupal/Core/Hook/Order/OrderAfter.php
new file mode 100644
index 0000000000000000000000000000000000000000..73dfd926475569138f81976c5c57e4dec7f53d13
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Order/OrderAfter.php
@@ -0,0 +1,19 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Order;
+
+/**
+ * Set this implementation to be after others.
+ */
+readonly class OrderAfter extends RelativeOrderBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function isAfter(): bool {
+    return TRUE;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Hook/Order/OrderBefore.php b/core/lib/Drupal/Core/Hook/Order/OrderBefore.php
new file mode 100644
index 0000000000000000000000000000000000000000..cc79560a3d5ab70d3b0087ef50c70b0b09b452e3
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Order/OrderBefore.php
@@ -0,0 +1,19 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Order;
+
+/**
+ * Set this implementation to be before others.
+ */
+readonly class OrderBefore extends RelativeOrderBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function isAfter(): bool {
+    return FALSE;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Hook/Order/OrderInterface.php b/core/lib/Drupal/Core/Hook/Order/OrderInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..85fc820a5c61466255678ebe933666a364ab15b0
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Order/OrderInterface.php
@@ -0,0 +1,42 @@
+<?php
+
+declare(strict_types = 1);
+
+namespace Drupal\Core\Hook\Order;
+
+use Drupal\Core\Hook\OrderOperation\OrderOperation;
+
+/**
+ * Interface for order specifiers used in hook attributes.
+ *
+ * Objects implementing this interface allow for relative ordering of hooks.
+ * These objects are passed as an order parameter to a Hook or ReorderHook
+ * attribute.
+ * Order::First and Order::Last are simple order operations that move the hook
+ * implementation to the first or last position of hooks at the time the order
+ * directive is executed.
+ *   @code
+ *   #[Hook('custom_hook', order: Order::First)]
+ *   @endcode
+ * OrderBefore and OrderAfter take additional parameters
+ * for ordering. See Drupal\Core\Hook\Order\RelativeOrderBase.
+ *   @code
+ *   #[Hook('custom_hook', order: new OrderBefore(['other_module']))]
+ *   @endcode
+ */
+interface OrderInterface {
+
+  /**
+   * Gets order operations specified by this object.
+   *
+   * @param string $identifier
+   *   Identifier of the implementation to move to a new position. The format
+   *   is the class followed by "::" then the method name. For example,
+   *   "Drupal\my_module\Hook\MyModuleHooks::methodName".
+   *
+   * @return \Drupal\Core\Hook\OrderOperation\OrderOperation
+   *   Order operation to apply to a hook implementation list.
+   */
+  public function getOperation(string $identifier): OrderOperation;
+
+}
diff --git a/core/lib/Drupal/Core/Hook/Order/RelativeOrderBase.php b/core/lib/Drupal/Core/Hook/Order/RelativeOrderBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..eaf6eade668d7d94d3c7e2560233d5720b82aa63
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Order/RelativeOrderBase.php
@@ -0,0 +1,56 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Order;
+
+use Drupal\Core\Hook\OrderOperation\BeforeOrAfter;
+use Drupal\Core\Hook\OrderOperation\OrderOperation;
+
+/**
+ * Orders an implementation relative to other implementations.
+ */
+abstract readonly class RelativeOrderBase implements OrderInterface {
+
+  /**
+   * Constructor.
+   *
+   * @param list<string> $modules
+   *   A list of modules the implementations should order against.
+   * @param list<array{class-string, string}> $classesAndMethods
+   *   A list of implementations to order against, as [$class, $method].
+   */
+  public function __construct(
+    public array $modules = [],
+    public array $classesAndMethods = [],
+  ) {
+    if (!$this->modules && !$this->classesAndMethods) {
+      throw new \LogicException('Order must provide either modules or class-method pairs to order against.');
+    }
+  }
+
+  /**
+   * Specifies the ordering direction.
+   *
+   * @return bool
+   *   TRUE, if the ordered implementation should be inserted after the
+   *   implementations specified in the constructor.
+   */
+  abstract protected function isAfter(): bool;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOperation(string $identifier): OrderOperation {
+    return new BeforeOrAfter(
+      $identifier,
+      $this->modules,
+      array_map(
+        static fn(array $class_and_method) => implode('::', $class_and_method),
+        $this->classesAndMethods,
+      ),
+      $this->isAfter(),
+    );
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Hook/OrderOperation/BeforeOrAfter.php b/core/lib/Drupal/Core/Hook/OrderOperation/BeforeOrAfter.php
new file mode 100644
index 0000000000000000000000000000000000000000..e87f661fc39257fc19b841404aa6981bf6c5e24d
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/OrderOperation/BeforeOrAfter.php
@@ -0,0 +1,81 @@
+<?php
+
+declare(strict_types = 1);
+
+namespace Drupal\Core\Hook\OrderOperation;
+
+/**
+ * Moves one listener to be called before or after other listeners.
+ *
+ * @internal
+ */
+class BeforeOrAfter extends OrderOperation {
+
+  /**
+   * Constructor.
+   *
+   * @param string $identifier
+   *   Identifier of the implementation to move to a new position. The format
+   *   is the class followed by "::" then the method name. For example,
+   *   "Drupal\my_module\Hook\MyModuleHooks::methodName".
+   * @param list<string> $modulesToOrderAgainst
+   *   Module names of listeners to order against.
+   * @param list<string> $identifiersToOrderAgainst
+   *   Identifiers of listeners to order against.
+   *   The format is "$class::$method".
+   * @param bool $isAfter
+   *   TRUE, if the listener to move should be moved after the listener to order
+   *   against, FALSE if it should be moved before.
+   */
+  public function __construct(
+    protected readonly string $identifier,
+    protected readonly array $modulesToOrderAgainst,
+    protected readonly array $identifiersToOrderAgainst,
+    protected readonly bool $isAfter,
+  ) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function apply(array &$identifiers, array $module_finder): void {
+    assert(array_is_list($identifiers));
+    $index = array_search($this->identifier, $identifiers);
+    if ($index === FALSE) {
+      // Nothing to reorder.
+      return;
+    }
+    $identifiers_to_order_against = $this->identifiersToOrderAgainst;
+    if ($this->modulesToOrderAgainst) {
+      $identifiers_to_order_against = [
+        ...$identifiers_to_order_against,
+        ...array_keys(array_intersect($module_finder, $this->modulesToOrderAgainst)),
+      ];
+    }
+    $indices_to_order_against = array_keys(array_intersect($identifiers, $identifiers_to_order_against));
+    if ($indices_to_order_against === []) {
+      return;
+    }
+    if ($this->isAfter) {
+      $max_index_to_order_against = max($indices_to_order_against);
+      if ($index >= $max_index_to_order_against) {
+        // The element is already after the other elements.
+        return;
+      }
+      array_splice($identifiers, $max_index_to_order_against + 1, 0, $this->identifier);
+      // Remove the element after splicing.
+      unset($identifiers[$index]);
+      $identifiers = array_values($identifiers);
+    }
+    else {
+      $min_index_to_order_against = min($indices_to_order_against);
+      if ($index <= $min_index_to_order_against) {
+        // The element is already before the other elements.
+        return;
+      }
+      // Remove the element before splicing.
+      unset($identifiers[$index]);
+      array_splice($identifiers, $min_index_to_order_against, 0, $this->identifier);
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Hook/OrderOperation/FirstOrLast.php b/core/lib/Drupal/Core/Hook/OrderOperation/FirstOrLast.php
new file mode 100644
index 0000000000000000000000000000000000000000..2169e533891da8255b721069020207d5dd7965e1
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/OrderOperation/FirstOrLast.php
@@ -0,0 +1,48 @@
+<?php
+
+declare(strict_types = 1);
+
+namespace Drupal\Core\Hook\OrderOperation;
+
+/**
+ * Moves one listener to the start or end of the list.
+ *
+ * @internal
+ */
+class FirstOrLast extends OrderOperation {
+
+  /**
+   * Constructor.
+   *
+   * @param string $identifier
+   *   Identifier of the implementation to move to a new position. The format
+   *   is the class followed by "::" then the method name. For example,
+   *   "Drupal\my_module\Hook\MyModuleHooks::methodName".
+   * @param bool $isLast
+   *   TRUE to move to the end, FALSE to move to the start.
+   */
+  public function __construct(
+    protected readonly string $identifier,
+    protected readonly bool $isLast,
+  ) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function apply(array &$identifiers, array $module_finder): void {
+    $index = array_search($this->identifier, $identifiers);
+    if ($index === FALSE) {
+      // The element does not exist.
+      return;
+    }
+    unset($identifiers[$index]);
+    if ($this->isLast) {
+      $identifiers[] = $this->identifier;
+    }
+    else {
+      $identifiers = [$this->identifier, ...$identifiers];
+    }
+    $identifiers = array_values($identifiers);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Hook/OrderOperation/OrderOperation.php b/core/lib/Drupal/Core/Hook/OrderOperation/OrderOperation.php
new file mode 100644
index 0000000000000000000000000000000000000000..617b05c7e72b3fac682db315f8a31d80172889a7
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/OrderOperation/OrderOperation.php
@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types = 1);
+
+namespace Drupal\Core\Hook\OrderOperation;
+
+/**
+ * Base class for order operations.
+ */
+abstract class OrderOperation {
+
+  /**
+   * Converts the operation to a structure that can be stored in the container.
+   *
+   * @return array
+   *   Packed operation.
+   */
+  final public function pack(): array {
+    $is_before_or_after = match(get_class($this)) {
+      BeforeOrAfter::class => TRUE,
+      FirstOrLast::class => FALSE,
+    };
+    return [$is_before_or_after, get_object_vars($this)];
+  }
+
+  /**
+   * Converts the stored operation to objects that can apply ordering rules.
+   *
+   * @param array $packed_operation
+   *   Packed operation.
+   *
+   * @return self
+   *   Unpacked operation.
+   */
+  final public static function unpack(array $packed_operation): self {
+    [$is_before_or_after, $args] = $packed_operation;
+    $class = $is_before_or_after ? BeforeOrAfter::class : FirstOrLast::class;
+    return new $class(...$args);
+  }
+
+}
diff --git a/core/modules/ckeditor5/ckeditor5.module b/core/modules/ckeditor5/ckeditor5.module
index 759900cda00c11169d17aefbd70b8b758a4350ac..86be6f62b2471e314cd517a28aced049ff35605a 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..7eb7601e3d8bc08dbbfd1f696f9cf5fb14c56ba6 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\Order\OrderAfter;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Asset\AttachedAssetsInterface;
 use Drupal\Core\Render\Element;
@@ -100,8 +101,19 @@ 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'],
+    )
+  )]
   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 97bdc664e2591c76bf2c40039358722e4eb7f653..49b15bb022bcd68d7b5a6bde8e0794f9c0e879ac 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..c681b07c0596271a3600491f724502802cfa87b2 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\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..28159482d3163382def10ec1a64a7909313dbdd7 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\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..397eedc8dad78d4c0ccd8076ac0e6fde4094732a 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\Order\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..3b94bc845733df3c27ac65a567916070a9d1dc23 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\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/HookCollector/aaa_hook_collector_test/aaa_hook_collector_test.info.yml b/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/aaa_hook_collector_test.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0ebae91b673501b386fdca9c31515021c192c01a
--- /dev/null
+++ b/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/aaa_hook_collector_test.info.yml
@@ -0,0 +1,7 @@
+name: AAA Hook collector test
+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/HookCollector/aaa_hook_collector_test/src/Hook/TestHookAfter.php b/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/src/Hook/TestHookAfter.php
new file mode 100644
index 0000000000000000000000000000000000000000..bf2ae7db46d3e7caed95d427c6d3d6449015f133
--- /dev/null
+++ b/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/src/Hook/TestHookAfter.php
@@ -0,0 +1,27 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\aaa_hook_collector_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Order\OrderAfter;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names. Some of the implementations are reordered
+ * using order attributes.
+ */
+class TestHookAfter {
+
+  /**
+   * This pair tests OrderAfter.
+   */
+  #[Hook('custom_hook_test_hook_after', order: new OrderAfter(['bbb_hook_collector_test']))]
+  public function hookAfter(): string {
+    return __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/src/Hook/TestHookAfterClassMethod.php b/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/src/Hook/TestHookAfterClassMethod.php
new file mode 100644
index 0000000000000000000000000000000000000000..e3861fe3516bd68433af2b84c7195204bef47f36
--- /dev/null
+++ b/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/src/Hook/TestHookAfterClassMethod.php
@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\aaa_hook_collector_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Order\OrderAfter;
+use Drupal\bbb_hook_collector_test\Hook\TestHookAfterClassMethod as TestHookAfterClassMethodForAfter;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names. Some of the implementations are reordered
+ * using order attributes.
+ */
+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(): string {
+    return __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/src/Hook/TestHookBefore.php b/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/src/Hook/TestHookBefore.php
new file mode 100644
index 0000000000000000000000000000000000000000..76661c5297d4adee4cf1fa7ecee9c3a9fdfc0057
--- /dev/null
+++ b/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/src/Hook/TestHookBefore.php
@@ -0,0 +1,27 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\aaa_hook_collector_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names. Some of the implementations are reordered
+ * using order attributes.
+ */
+class TestHookBefore {
+
+  /**
+   * This pair tests OrderBefore.
+   */
+  #[Hook('custom_hook_test_hook_before')]
+  public function hookBefore(): string {
+    // This should be run second, there is another hook reordering before this.
+    return __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/src/Hook/TestHookFirst.php b/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/src/Hook/TestHookFirst.php
new file mode 100644
index 0000000000000000000000000000000000000000..e3ce6b86963a1209fa37b3ec2fd979db74243107
--- /dev/null
+++ b/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/src/Hook/TestHookFirst.php
@@ -0,0 +1,27 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\aaa_hook_collector_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names. Some of the implementations are reordered
+ * using order attributes.
+ */
+class TestHookFirst {
+
+  /**
+   * This pair tests OrderFirst.
+   */
+  #[Hook('custom_hook_test_hook_first')]
+  public function hookFirst(): string {
+    // This should be run second, there is another hook reordering before this.
+    return __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/src/Hook/TestHookLast.php b/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/src/Hook/TestHookLast.php
new file mode 100644
index 0000000000000000000000000000000000000000..427422b378f538b89ee19f27fe04fcf5b0fd1802
--- /dev/null
+++ b/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/src/Hook/TestHookLast.php
@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\aaa_hook_collector_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Order\Order;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names. Some of the implementations are reordered
+ * using order attributes.
+ */
+class TestHookLast {
+
+  /**
+   * This pair tests OrderLast.
+   */
+  #[Hook('custom_hook_test_hook_last', order: Order::Last)]
+  public function hookLast(): string {
+    // This should be run after.
+    return __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/src/Hook/TestHookOrderExtraTypes.php b/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/src/Hook/TestHookOrderExtraTypes.php
new file mode 100644
index 0000000000000000000000000000000000000000..eb2c35472f0382581b6dffedfd5fac0363ecd39c
--- /dev/null
+++ b/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/src/Hook/TestHookOrderExtraTypes.php
@@ -0,0 +1,32 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\aaa_hook_collector_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Order\OrderAfter;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names. Some of the implementations are reordered
+ * using order attributes.
+ */
+class TestHookOrderExtraTypes {
+
+  /**
+   * This pair tests OrderAfter with ExtraTypes.
+   */
+  #[Hook('custom_hook_extra_types1_alter',
+    order: new OrderAfter(
+      modules: ['bbb_hook_collector_test'],
+    )
+  )]
+  public function customHookExtraTypes(array &$calls): void {
+    // This should be run after.
+    $calls[] = __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/src/Hook/TestHookReorderHookFirst.php b/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/src/Hook/TestHookReorderHookFirst.php
new file mode 100644
index 0000000000000000000000000000000000000000..55e12aad240f97737975c8e55f28f39cadcc93d7
--- /dev/null
+++ b/core/modules/system/tests/modules/HookCollector/aaa_hook_collector_test/src/Hook/TestHookReorderHookFirst.php
@@ -0,0 +1,40 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\aaa_hook_collector_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Order\OrderAfter;
+use Drupal\Core\Hook\Attribute\ReorderHook;
+use Drupal\bbb_hook_collector_test\Hook\TestHookReorderHookLast;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names. Some of the implementations are reordered
+ * using order attributes.
+ */
+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 function customHookOverride(): string {
+    // This normally would run first.
+    // We override that order in hook_order_second_alphabetically.
+    // We override, that order here with ReorderHook.
+    return __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/bbb_hook_collector_test.info.yml b/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/bbb_hook_collector_test.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..a084845fb14fc3230650cb17288cf7b3a74b63e5
--- /dev/null
+++ b/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/bbb_hook_collector_test.info.yml
@@ -0,0 +1,7 @@
+name: BBB Hook collector test
+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/HookCollector/bbb_hook_collector_test/src/Hook/TestHookAfter.php b/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/src/Hook/TestHookAfter.php
new file mode 100644
index 0000000000000000000000000000000000000000..89a6f3a1b05b2212fde3932c4a304995bd69eff5
--- /dev/null
+++ b/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/src/Hook/TestHookAfter.php
@@ -0,0 +1,27 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\bbb_hook_collector_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names. Some of the implementations are reordered
+ * using order attributes.
+ */
+class TestHookAfter {
+
+  /**
+   * This pair tests OrderAfter.
+   */
+  #[Hook('custom_hook_test_hook_after')]
+  public function hookAfter(): string {
+    // This should be run before.
+    return __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/src/Hook/TestHookAfterClassMethod.php b/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/src/Hook/TestHookAfterClassMethod.php
new file mode 100644
index 0000000000000000000000000000000000000000..1cedcdcaa66c0f7336779a40b45ba8a17a7cae96
--- /dev/null
+++ b/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/src/Hook/TestHookAfterClassMethod.php
@@ -0,0 +1,27 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\bbb_hook_collector_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names. Some of the implementations are reordered
+ * using order attributes.
+ */
+class TestHookAfterClassMethod {
+
+  /**
+   * This pair tests OrderAfter with a passed class and method.
+   */
+  #[Hook('custom_hook_test_hook_after_class_method')]
+  public static function hookAfterClassMethod(): string {
+    // This should be run first since another hook overrides the natural order.
+    return __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/src/Hook/TestHookBefore.php b/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/src/Hook/TestHookBefore.php
new file mode 100644
index 0000000000000000000000000000000000000000..12341cd27d21b0f241646aa74b1a2c42dcdf0da8
--- /dev/null
+++ b/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/src/Hook/TestHookBefore.php
@@ -0,0 +1,27 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\bbb_hook_collector_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Order\OrderBefore;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names. Some of the implementations are reordered
+ * using order attributes.
+ */
+class TestHookBefore {
+
+  /**
+   * This pair tests OrderBefore.
+   */
+  #[Hook('custom_hook_test_hook_before', order: new OrderBefore(['aaa_hook_collector_test']))]
+  public function hookBefore(): string {
+    return __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/src/Hook/TestHookFirst.php b/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/src/Hook/TestHookFirst.php
new file mode 100644
index 0000000000000000000000000000000000000000..ac72de1da8042fe4c269e689ec3eeb190752be9a
--- /dev/null
+++ b/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/src/Hook/TestHookFirst.php
@@ -0,0 +1,27 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\bbb_hook_collector_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Order\Order;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names. Some of the implementations are reordered
+ * using order attributes.
+ */
+class TestHookFirst {
+
+  /**
+   * This pair tests OrderFirst.
+   */
+  #[Hook('custom_hook_test_hook_first', order: Order::First)]
+  public function hookFirst(): string {
+    return __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/src/Hook/TestHookLast.php b/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/src/Hook/TestHookLast.php
new file mode 100644
index 0000000000000000000000000000000000000000..6b30344f583d888557064529530c06e96ff7ec55
--- /dev/null
+++ b/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/src/Hook/TestHookLast.php
@@ -0,0 +1,26 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\bbb_hook_collector_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names. Some of the implementations are reordered
+ * using order attributes.
+ */
+class TestHookLast {
+
+  /**
+   * This pair tests OrderLast.
+   */
+  #[Hook('custom_hook_test_hook_last')]
+  public function hookLast(): string {
+    return __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/src/Hook/TestHookOrderExtraTypes.php b/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/src/Hook/TestHookOrderExtraTypes.php
new file mode 100644
index 0000000000000000000000000000000000000000..a32e3529ec6f0c5c6164e3f953bb1bca2ebfbebf
--- /dev/null
+++ b/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/src/Hook/TestHookOrderExtraTypes.php
@@ -0,0 +1,26 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\bbb_hook_collector_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names. Some of the implementations are reordered
+ * using order attributes.
+ */
+class TestHookOrderExtraTypes {
+
+  /**
+   * This pair tests OrderAfter with ExtraTypes.
+   */
+  #[Hook('custom_hook_extra_types2_alter')]
+  public function customHookExtraTypes(array &$calls): void {
+    $calls[] = __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/src/Hook/TestHookReorderHookLast.php b/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/src/Hook/TestHookReorderHookLast.php
new file mode 100644
index 0000000000000000000000000000000000000000..3b89394347a8981e87586906e243748ab06a3399
--- /dev/null
+++ b/core/modules/system/tests/modules/HookCollector/bbb_hook_collector_test/src/Hook/TestHookReorderHookLast.php
@@ -0,0 +1,31 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\bbb_hook_collector_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Order\Order;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names. Some of the implementations are reordered
+ * using order attributes.
+ */
+class TestHookReorderHookLast {
+
+  /**
+   * This pair tests ReorderHook.
+   */
+  #[Hook('custom_hook_override', order: Order::First)]
+  public function customHookOverride(): string {
+    // This normally would run second.
+    // We override that order here with Order::First.
+    // We override, that order in aaa_hook_collector_test with
+    // ReorderHook.
+    return __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookOrder/aaa_hook_order_test/aaa_hook_order_test.info.yml b/core/modules/system/tests/modules/HookOrder/aaa_hook_order_test/aaa_hook_order_test.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..0cc98c51c0b6f29b5374336da36586aef4312a43
--- /dev/null
+++ b/core/modules/system/tests/modules/HookOrder/aaa_hook_order_test/aaa_hook_order_test.info.yml
@@ -0,0 +1,6 @@
+name: AAA Hook order test
+type: module
+description: 'Test module used to test hook ordering.'
+package: Testing
+version: VERSION
+core_version_requirement: '*'
diff --git a/core/modules/system/tests/modules/HookOrder/aaa_hook_order_test/aaa_hook_order_test.module b/core/modules/system/tests/modules/HookOrder/aaa_hook_order_test/aaa_hook_order_test.module
new file mode 100644
index 0000000000000000000000000000000000000000..513a72c21f4413c00bad3635b63876935d9d2046
--- /dev/null
+++ b/core/modules/system/tests/modules/HookOrder/aaa_hook_order_test/aaa_hook_order_test.module
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Contains procedural hook implementations.
+ */
+
+declare(strict_types=1);
+
+use Drupal\aaa_hook_order_test\Hook\ModuleImplementsAlter;
+
+/**
+ * Implements hook_test_hook().
+ */
+function aaa_hook_order_test_test_hook(): string {
+  return __FUNCTION__;
+}
+
+/**
+ * Implements hook_sparse_test_hook().
+ */
+function aaa_hook_order_test_sparse_test_hook(): string {
+  return __FUNCTION__;
+}
+
+/**
+ * Implements hook_procedural_alter().
+ */
+function aaa_hook_order_test_procedural_alter(array &$calls): void {
+  $calls[] = __FUNCTION__;
+}
+
+/**
+ * Implements hook_procedural_subtype_alter().
+ */
+function aaa_hook_order_test_procedural_subtype_alter(array &$calls): void {
+  $calls[] = __FUNCTION__;
+}
+
+/**
+ * Implements hook_module_implements_alter().
+ */
+function aaa_hook_order_test_module_implements_alter(array &$implementations, string $hook): void {
+  ModuleImplementsAlter::call($implementations, $hook);
+}
diff --git a/core/modules/system/tests/modules/HookOrder/aaa_hook_order_test/src/Hook/AAlterHooks.php b/core/modules/system/tests/modules/HookOrder/aaa_hook_order_test/src/Hook/AAlterHooks.php
new file mode 100644
index 0000000000000000000000000000000000000000..f9e124e217b2c61120422a8b6f8b510a145e1691
--- /dev/null
+++ b/core/modules/system/tests/modules/HookOrder/aaa_hook_order_test/src/Hook/AAlterHooks.php
@@ -0,0 +1,29 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\aaa_hook_order_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Order\OrderAfter;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names. Some of the implementations are reordered
+ * using order attributes.
+ */
+class AAlterHooks {
+
+  #[Hook('test_alter', order: new OrderAfter(modules: ['ccc_hook_order_test']))]
+  public function testAlterAfterC(array &$calls): void {
+    $calls[] = __METHOD__;
+  }
+
+  #[Hook('test_subtype_alter')]
+  public function testSubtypeAlter(array &$calls): void {
+    $calls[] = __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookOrder/aaa_hook_order_test/src/Hook/AHooks.php b/core/modules/system/tests/modules/HookOrder/aaa_hook_order_test/src/Hook/AHooks.php
new file mode 100644
index 0000000000000000000000000000000000000000..234264cbbe55a43d928ec02ad18c9fed81668897
--- /dev/null
+++ b/core/modules/system/tests/modules/HookOrder/aaa_hook_order_test/src/Hook/AHooks.php
@@ -0,0 +1,40 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\aaa_hook_order_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Order\Order;
+use Drupal\Core\Hook\Order\OrderAfter;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names. Some of the implementations are reordered
+ * using order attributes.
+ */
+class AHooks {
+
+  #[Hook('test_hook')]
+  public function testHook(): string {
+    return __METHOD__;
+  }
+
+  #[Hook('test_hook', order: Order::First)]
+  public function testHookFirst(): string {
+    return __METHOD__;
+  }
+
+  #[Hook('test_hook', order: Order::Last)]
+  public function testHookLast(): string {
+    return __METHOD__;
+  }
+
+  #[Hook('test_hook', order: new OrderAfter(modules: ['bbb_hook_order_test']))]
+  public function testHookAfterB(): string {
+    return __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookOrder/aaa_hook_order_test/src/Hook/ModuleImplementsAlter.php b/core/modules/system/tests/modules/HookOrder/aaa_hook_order_test/src/Hook/ModuleImplementsAlter.php
new file mode 100644
index 0000000000000000000000000000000000000000..76d3912bdebef389be940431855958a9c9633a45
--- /dev/null
+++ b/core/modules/system/tests/modules/HookOrder/aaa_hook_order_test/src/Hook/ModuleImplementsAlter.php
@@ -0,0 +1,47 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\aaa_hook_order_test\Hook;
+
+/**
+ * Contains a replaceable callback for hook_module_implements_alter().
+ */
+class ModuleImplementsAlter {
+
+  /**
+   * Callback for hook_module_implements_alter().
+   *
+   * @var ?\Closure
+   * @phpstan-var (\Closure(array<string, string|false>&, string): void)|null
+   */
+  private static ?\Closure $callback = NULL;
+
+  /**
+   * Sets a callback for hook_module_implements_alter().
+   *
+   * @param ?\Closure $callback
+   *   Callback to set, or NULL to unset.
+   *
+   * @phpstan-param (\Closure(array<string, string|false>&, string): void)|null $callback
+   */
+  public static function set(?\Closure $callback): void {
+    self::$callback = $callback;
+  }
+
+  /**
+   * Invokes the registered callback.
+   *
+   * @param array<string, string|false> $implementations
+   *   The implementations, as "group" by module name.
+   * @param string $hook
+   *   The hook.
+   */
+  public static function call(array &$implementations, string $hook): void {
+    if (self::$callback === NULL) {
+      return;
+    }
+    (self::$callback)($implementations, $hook);
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookOrder/bbb_hook_order_test/bbb_hook_order_test.info.yml b/core/modules/system/tests/modules/HookOrder/bbb_hook_order_test/bbb_hook_order_test.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4b0667172e64732ccba5c7858048291b8bffc98a
--- /dev/null
+++ b/core/modules/system/tests/modules/HookOrder/bbb_hook_order_test/bbb_hook_order_test.info.yml
@@ -0,0 +1,6 @@
+name: BBB Hook order test
+type: module
+description: 'Test module used to test hook ordering.'
+package: Testing
+version: VERSION
+core_version_requirement: '*'
diff --git a/core/modules/system/tests/modules/HookOrder/bbb_hook_order_test/bbb_hook_order_test.module b/core/modules/system/tests/modules/HookOrder/bbb_hook_order_test/bbb_hook_order_test.module
new file mode 100644
index 0000000000000000000000000000000000000000..c2ac51b2f29c9f2a89d933cf0afa55202a17faad
--- /dev/null
+++ b/core/modules/system/tests/modules/HookOrder/bbb_hook_order_test/bbb_hook_order_test.module
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains procedural hook implementations.
+ */
+
+declare(strict_types=1);
+
+/**
+ * Implements hook_test_hook().
+ */
+function bbb_hook_order_test_test_hook(): string {
+  return __FUNCTION__;
+}
+
+/**
+ * Implements hook_procedural_alter().
+ */
+function bbb_hook_order_test_procedural_alter(array &$calls): void {
+  $calls[] = __FUNCTION__;
+}
+
+/**
+ * Implements hook_procedural_subtype_alter().
+ */
+function bbb_hook_order_test_procedural_subtype_alter(array &$calls): void {
+  $calls[] = __FUNCTION__;
+}
diff --git a/core/modules/system/tests/modules/HookOrder/bbb_hook_order_test/src/Hook/BAlterHooks.php b/core/modules/system/tests/modules/HookOrder/bbb_hook_order_test/src/Hook/BAlterHooks.php
new file mode 100644
index 0000000000000000000000000000000000000000..0be826094ebfc3e664ec9691eed63a9fd424786a
--- /dev/null
+++ b/core/modules/system/tests/modules/HookOrder/bbb_hook_order_test/src/Hook/BAlterHooks.php
@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\bbb_hook_order_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names. Some of the implementations are reordered
+ * using order attributes.
+ */
+class BAlterHooks {
+
+  #[Hook('test_subtype_alter')]
+  public function testSubtypeAlter(array &$calls): void {
+    $calls[] = __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookOrder/bbb_hook_order_test/src/Hook/BHooks.php b/core/modules/system/tests/modules/HookOrder/bbb_hook_order_test/src/Hook/BHooks.php
new file mode 100644
index 0000000000000000000000000000000000000000..a93643e45e4b7f9398479705bbfe8e408a04e3e6
--- /dev/null
+++ b/core/modules/system/tests/modules/HookOrder/bbb_hook_order_test/src/Hook/BHooks.php
@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\bbb_hook_order_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names. Some of the implementations are reordered
+ * using order attributes.
+ */
+class BHooks {
+
+  #[Hook('test_hook')]
+  public function testHook(): string {
+    return __METHOD__;
+  }
+
+  #[Hook('sparse_test_hook')]
+  public function sparseTestHook(): string {
+    return __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookOrder/ccc_hook_order_test/ccc_hook_order_test.info.yml b/core/modules/system/tests/modules/HookOrder/ccc_hook_order_test/ccc_hook_order_test.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d20a7a36ab1fe70abfb53ef2e952471529b919f0
--- /dev/null
+++ b/core/modules/system/tests/modules/HookOrder/ccc_hook_order_test/ccc_hook_order_test.info.yml
@@ -0,0 +1,6 @@
+name: CCC Hook order test
+type: module
+description: 'Test module used to test hook ordering.'
+package: Testing
+version: VERSION
+core_version_requirement: '*'
diff --git a/core/modules/system/tests/modules/HookOrder/ccc_hook_order_test/ccc_hook_order_test.module b/core/modules/system/tests/modules/HookOrder/ccc_hook_order_test/ccc_hook_order_test.module
new file mode 100644
index 0000000000000000000000000000000000000000..3c6246298b727bb88d9be32fda2e1f92e9693a63
--- /dev/null
+++ b/core/modules/system/tests/modules/HookOrder/ccc_hook_order_test/ccc_hook_order_test.module
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains procedural hook implementations.
+ */
+
+declare(strict_types=1);
+
+/**
+ * Implements hook_test_hook().
+ */
+function ccc_hook_order_test_test_hook(): string {
+  return __FUNCTION__;
+}
+
+/**
+ * Implements hook_sparse_test_hook().
+ */
+function ccc_hook_order_test_sparse_test_hook(): string {
+  return __FUNCTION__;
+}
+
+/**
+ * Implements hook_procedural_alter().
+ */
+function ccc_hook_order_test_procedural_alter(array &$calls): void {
+  $calls[] = __FUNCTION__;
+}
+
+/**
+ * Implements hook_procedural_subtype_alter().
+ */
+function ccc_hook_order_test_procedural_subtype_alter(array &$calls): void {
+  $calls[] = __FUNCTION__;
+}
diff --git a/core/modules/system/tests/modules/HookOrder/ccc_hook_order_test/src/Hook/CAlterHooks.php b/core/modules/system/tests/modules/HookOrder/ccc_hook_order_test/src/Hook/CAlterHooks.php
new file mode 100644
index 0000000000000000000000000000000000000000..f005daa0a38b5547d8007251a895a1a6b6ade7c4
--- /dev/null
+++ b/core/modules/system/tests/modules/HookOrder/ccc_hook_order_test/src/Hook/CAlterHooks.php
@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\ccc_hook_order_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names. Some of the implementations are reordered
+ * using order attributes.
+ */
+class CAlterHooks {
+
+  #[Hook('test_alter')]
+  public function testAlter(array &$calls): void {
+    $calls[] = __METHOD__;
+  }
+
+  #[Hook('test_subtype_alter')]
+  public function testSubtypeAlter(array &$calls): void {
+    $calls[] = __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookOrder/ccc_hook_order_test/src/Hook/CHooks.php b/core/modules/system/tests/modules/HookOrder/ccc_hook_order_test/src/Hook/CHooks.php
new file mode 100644
index 0000000000000000000000000000000000000000..b3571d490832834a0cf58b78e5e0a5e59861709f
--- /dev/null
+++ b/core/modules/system/tests/modules/HookOrder/ccc_hook_order_test/src/Hook/CHooks.php
@@ -0,0 +1,49 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\ccc_hook_order_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Order\Order;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names. Some of the implementations are reordered
+ * using order attributes.
+ */
+class CHooks {
+
+  #[Hook('test_hook')]
+  public function testHook(): string {
+    return __METHOD__;
+  }
+
+  #[Hook('test_hook', order: Order::First)]
+  public function testHookFirst(): string {
+    return __METHOD__;
+  }
+
+  /**
+   * This implementation is reordered from elsewhere.
+   *
+   * @see \Drupal\ddd_hook_order_test\Hook\DHooks
+   */
+  #[Hook('test_hook')]
+  public function testHookReorderFirst(): string {
+    return __METHOD__;
+  }
+
+  /**
+   * This implementation is removed from elsewhere.
+   *
+   * @see \Drupal\ddd_hook_order_test\Hook\DHooks
+   */
+  #[Hook('test_hook')]
+  public function testHookRemoved(): string {
+    return __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookOrder/ddd_hook_order_test/ddd_hook_order_test.info.yml b/core/modules/system/tests/modules/HookOrder/ddd_hook_order_test/ddd_hook_order_test.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..df2c987a667260c26fbd0de3355e817ed120ade0
--- /dev/null
+++ b/core/modules/system/tests/modules/HookOrder/ddd_hook_order_test/ddd_hook_order_test.info.yml
@@ -0,0 +1,6 @@
+name: DDD Hook order test
+type: module
+description: 'Test module used to test hook ordering.'
+package: Testing
+version: VERSION
+core_version_requirement: '*'
diff --git a/core/modules/system/tests/modules/HookOrder/ddd_hook_order_test/ddd_hook_order_test.module b/core/modules/system/tests/modules/HookOrder/ddd_hook_order_test/ddd_hook_order_test.module
new file mode 100644
index 0000000000000000000000000000000000000000..82cccc7ba6c3ada03a485d01fcd0a5d3aa37ce1c
--- /dev/null
+++ b/core/modules/system/tests/modules/HookOrder/ddd_hook_order_test/ddd_hook_order_test.module
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Contains procedural hook implementations.
+ */
+
+declare(strict_types=1);
+
+/**
+ * Implements hook_test_hook().
+ */
+function ddd_hook_order_test_test_hook(): string {
+  return __FUNCTION__;
+}
diff --git a/core/modules/system/tests/modules/HookOrder/ddd_hook_order_test/src/Hook/DAlterHooks.php b/core/modules/system/tests/modules/HookOrder/ddd_hook_order_test/src/Hook/DAlterHooks.php
new file mode 100644
index 0000000000000000000000000000000000000000..7da7d91002cd7e3f5d7dbd372837134a5c37de6e
--- /dev/null
+++ b/core/modules/system/tests/modules/HookOrder/ddd_hook_order_test/src/Hook/DAlterHooks.php
@@ -0,0 +1,27 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\ddd_hook_order_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names.
+ */
+class DAlterHooks {
+
+  #[Hook('test_alter')]
+  public function testAlter(array &$calls): void {
+    $calls[] = __METHOD__;
+  }
+
+  #[Hook('test_subtype_alter')]
+  public function testSubtypeAlter(array &$calls): void {
+    $calls[] = __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/HookOrder/ddd_hook_order_test/src/Hook/DHooks.php b/core/modules/system/tests/modules/HookOrder/ddd_hook_order_test/src/Hook/DHooks.php
new file mode 100644
index 0000000000000000000000000000000000000000..053228642d6474e4b14bc8da23f273613248a827
--- /dev/null
+++ b/core/modules/system/tests/modules/HookOrder/ddd_hook_order_test/src/Hook/DHooks.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\ddd_hook_order_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Attribute\RemoveHook;
+use Drupal\Core\Hook\Attribute\ReorderHook;
+use Drupal\Core\Hook\Order\Order;
+use Drupal\ccc_hook_order_test\Hook\CHooks;
+
+/**
+ * This class contains hook implementations.
+ *
+ * By default, these will be called in module order, which is predictable due
+ * to the alphabetical module names.
+ */
+#[ReorderHook('test_hook', CHooks::class, 'testHookReorderFirst', Order::First)]
+#[RemoveHook('test_hook', CHooks::class, 'testHookRemoved')]
+class DHooks {
+
+  #[Hook('test_hook')]
+  public function testHook(): string {
+    return __METHOD__;
+  }
+
+  #[Hook('sparse_test_hook')]
+  public function sparseTestHook(): string {
+    return __METHOD__;
+  }
+
+}
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 4e978472953ba50b82fa69c6e4337d4d6f0a94cd..5d7fdc3dc6b8d0150d2127d379d7ef0d5db77430 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_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..3d4e53eba0bbd0418cbdfc627ab38ab87fa47434
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_test_remove/src/Hook/TestHookRemove.php
@@ -0,0 +1,38 @@
+<?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 function hookDoNotRun(): string {
+    // This hook should not run.
+    return __METHOD__;
+  }
+
+  /**
+   * This hook should run and prevent custom_hook1.
+   */
+  #[Hook('custom_hook1')]
+  #[RemoveHook(
+    'custom_hook1',
+    class: TestHookRemove::class,
+    method: 'hookDoNotRun'
+  )]
+  public function hookDoRun(): string {
+    // This hook should run.
+    return __METHOD__;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/module_test/module_test.implementations.inc b/core/modules/system/tests/modules/module_implements_alter_test/module_implements_alter_test.implementations.inc
similarity index 54%
rename from core/modules/system/tests/modules/module_test/module_test.implementations.inc
rename to core/modules/system/tests/modules/module_implements_alter_test/module_implements_alter_test.implementations.inc
index b971af58c8c1781b2ee9e0c53eceacc4a49b4c49..7b6bac4ae95dc2d23564c4b807dfa352a3e263be 100644
--- a/core/modules/system/tests/modules/module_test/module_test.implementations.inc
+++ b/core/modules/system/tests/modules/module_implements_alter_test/module_implements_alter_test.implementations.inc
@@ -10,8 +10,8 @@
 /**
  * Implements hook_altered_test_hook().
  *
- * @see module_test_module_implements_alter()
+ * @see module_implements_alter_test_module_implements_alter()
  */
-function module_test_altered_test_hook(): string {
+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..c379d30c360d20279deb00a4c1e88a016f82a02b
--- /dev/null
+++ b/core/modules/system/tests/modules/module_implements_alter_test/module_implements_alter_test.module
@@ -0,0 +1,41 @@
+<?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/workspaces/src/Hook/EntityOperations.php b/core/modules/workspaces/src/Hook/EntityOperations.php
index c459f4d065e3fbd47998ffa6941a561452993034..377ea62b2e88aae74310b3dc28e3bca43d05971a 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\Order;
+use Drupal\Core\Hook\Order\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..2a8ad684b5385ed25235d2ba48fa047d42ec951f 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,32 @@ 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());
-
-    $this->assertTrue(\Drupal::moduleHandler()->hasImplementations('modules_installed', 'module_test'),
-      'module_test implements hook_modules_installed().');
+    // 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('module_implements_alter', 'module_test'),
-      'module_test implements hook_module_implements_alter().');
+    $this->assertTrue(\Drupal::moduleHandler()->hasImplementations('module_implements_alter', 'module_implements_alter_test'),
+      'module_implements_alter_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_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.');
+    // 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.');
   }
 
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Hook/HookAlterOrderTest.php b/core/tests/Drupal/KernelTests/Core/Hook/HookAlterOrderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a96040e903569839914b684694fcce65d0489f73
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Hook/HookAlterOrderTest.php
@@ -0,0 +1,226 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\KernelTests\Core\Hook;
+
+use Drupal\aaa_hook_order_test\Hook\AAlterHooks;
+use Drupal\aaa_hook_order_test\Hook\ModuleImplementsAlter;
+use Drupal\bbb_hook_order_test\Hook\BAlterHooks;
+use Drupal\ccc_hook_order_test\Hook\CAlterHooks;
+use Drupal\ddd_hook_order_test\Hook\DAlterHooks;
+use Drupal\KernelTests\KernelTestBase;
+use PHPUnit\Framework\Attributes\IgnoreDeprecations;
+
+/**
+ * @group Hook
+ */
+#[IgnoreDeprecations]
+class HookAlterOrderTest extends KernelTestBase {
+
+  use HookOrderTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'aaa_hook_order_test',
+    'bbb_hook_order_test',
+    'ccc_hook_order_test',
+    'ddd_hook_order_test',
+  ];
+
+  public function testProceduralModuleImplementsAlterOrder(): void {
+    $this->assertAlterCallOrder($main_unaltered = [
+      'aaa_hook_order_test_procedural_alter',
+      'bbb_hook_order_test_procedural_alter',
+      'ccc_hook_order_test_procedural_alter',
+    ], 'procedural');
+
+    $this->assertAlterCallOrder($sub_unaltered = [
+      'aaa_hook_order_test_procedural_subtype_alter',
+      'bbb_hook_order_test_procedural_subtype_alter',
+      'ccc_hook_order_test_procedural_subtype_alter',
+    ], 'procedural_subtype');
+
+    $this->assertAlterCallOrder($combined_unaltered = [
+      'aaa_hook_order_test_procedural_alter',
+      'aaa_hook_order_test_procedural_subtype_alter',
+      'bbb_hook_order_test_procedural_alter',
+      'bbb_hook_order_test_procedural_subtype_alter',
+      'ccc_hook_order_test_procedural_alter',
+      'ccc_hook_order_test_procedural_subtype_alter',
+    ], ['procedural', 'procedural_subtype']);
+
+    $move_b_down = function (array &$implementations): void {
+      // Move B to the end, no matter which hook.
+      $group = $implementations['bbb_hook_order_test'];
+      unset($implementations['bbb_hook_order_test']);
+      $implementations['bbb_hook_order_test'] = $group;
+    };
+    $modules = ['aaa_hook_order_test', 'bbb_hook_order_test', 'ccc_hook_order_test'];
+
+    // Test with module B moved to the end for both hooks.
+    ModuleImplementsAlter::set(
+      function (array &$implementations, string $hook) use ($modules, $move_b_down): void {
+        if (!in_array($hook, ['procedural_alter', 'procedural_subtype_alter'])) {
+          return;
+        }
+        $this->assertSame($modules, array_keys($implementations));
+        $move_b_down($implementations);
+      },
+    );
+    \Drupal::service('kernel')->rebuildContainer();
+
+    $this->assertAlterCallOrder($main_altered = [
+      'aaa_hook_order_test_procedural_alter',
+      'ccc_hook_order_test_procedural_alter',
+      // The implementation of B has been moved.
+      'bbb_hook_order_test_procedural_alter',
+    ], 'procedural');
+
+    $this->assertAlterCallOrder($sub_altered = [
+      'aaa_hook_order_test_procedural_subtype_alter',
+      'ccc_hook_order_test_procedural_subtype_alter',
+      // The implementation of B has been moved.
+      'bbb_hook_order_test_procedural_subtype_alter',
+    ], 'procedural_subtype');
+
+    $this->assertAlterCallOrder($combined_altered = [
+      'aaa_hook_order_test_procedural_alter',
+      'aaa_hook_order_test_procedural_subtype_alter',
+      'ccc_hook_order_test_procedural_alter',
+      'ccc_hook_order_test_procedural_subtype_alter',
+      // The implementation of B has been moved.
+      'bbb_hook_order_test_procedural_alter',
+      'bbb_hook_order_test_procedural_subtype_alter',
+    ], ['procedural', 'procedural_subtype']);
+
+    // If the altered hook is not the first one, implementations are back in
+    // their unaltered order.
+    $this->assertAlterCallOrder($main_unaltered, ['other_main_type', 'procedural']);
+    $this->assertAlterCallOrder($sub_unaltered, ['other_main_type', 'procedural_subtype']);
+    $this->assertAlterCallOrder($combined_unaltered, ['other_main_type', 'procedural', 'procedural_subtype']);
+
+    // Test with module B moved to the end for the main hook.
+    ModuleImplementsAlter::set(
+      function (array &$implementations, string $hook) use ($modules, $move_b_down): void {
+        if (!in_array($hook, ['procedural_alter', 'procedural_subtype_alter'])) {
+          return;
+        }
+        $this->assertSame($modules, array_keys($implementations));
+        if ($hook !== 'procedural_alter') {
+          return;
+        }
+        $move_b_down($implementations);
+      },
+    );
+    \Drupal::service('kernel')->rebuildContainer();
+
+    $this->assertAlterCallOrder($main_altered, 'procedural');
+    $this->assertAlterCallOrder($sub_unaltered, 'procedural_subtype');
+    $this->assertAlterCallOrder($combined_altered, ['procedural', 'procedural_subtype']);
+
+    // Test with module B moved to the end for the subtype hook.
+    ModuleImplementsAlter::set(
+      function (array &$implementations, string $hook) use ($modules, $move_b_down): void {
+        if (!in_array($hook, ['procedural_alter', 'procedural_subtype_alter'])) {
+          return;
+        }
+        $this->assertSameCallList($modules, array_keys($implementations));
+        if ($hook !== 'procedural_subtype_alter') {
+          return;
+        }
+        $move_b_down($implementations);
+      },
+    );
+    \Drupal::service('kernel')->rebuildContainer();
+
+    $this->assertAlterCallOrder($main_unaltered, 'procedural');
+    $this->assertAlterCallOrder($sub_altered, 'procedural_subtype');
+    $this->assertAlterCallOrder($combined_unaltered, ['procedural', 'procedural_subtype']);
+  }
+
+  public function testAlterOrder(): void {
+    $this->assertAlterCallOrder([
+      CAlterHooks::class . '::testAlter',
+      AAlterHooks::class . '::testAlterAfterC',
+      DAlterHooks::class . '::testAlter',
+    ], 'test');
+
+    $this->assertAlterCallOrder([
+      AAlterHooks::class . '::testSubtypeAlter',
+      BAlterHooks::class . '::testSubtypeAlter',
+      CAlterHooks::class . '::testSubtypeAlter',
+      DAlterHooks::class . '::testSubtypeAlter',
+    ], 'test_subtype');
+
+    $this->assertAlterCallOrder([
+      // The implementation from 'D' is gone.
+      AAlterHooks::class . '::testSubtypeAlter',
+      BAlterHooks::class . '::testSubtypeAlter',
+      CAlterHooks::class . '::testAlter',
+      CAlterHooks::class . '::testSubtypeAlter',
+      AAlterHooks::class . '::testAlterAfterC',
+      DAlterHooks::class . '::testAlter',
+      DAlterHooks::class . '::testSubtypeAlter',
+    ], ['test', 'test_subtype']);
+
+    $this->disableModules(['bbb_hook_order_test']);
+
+    $this->assertAlterCallOrder([
+      CAlterHooks::class . '::testAlter',
+      AAlterHooks::class . '::testAlterAfterC',
+      DAlterHooks::class . '::testAlter',
+    ], 'test');
+
+    $this->assertAlterCallOrder([
+      AAlterHooks::class . '::testSubtypeAlter',
+      CAlterHooks::class . '::testSubtypeAlter',
+      DAlterHooks::class . '::testSubtypeAlter',
+    ], 'test_subtype');
+
+    $this->assertAlterCallOrder([
+      AAlterHooks::class . '::testSubtypeAlter',
+      CAlterHooks::class . '::testAlter',
+      CAlterHooks::class . '::testSubtypeAlter',
+      AAlterHooks::class . '::testAlterAfterC',
+      DAlterHooks::class . '::testAlter',
+      DAlterHooks::class . '::testSubtypeAlter',
+    ], ['test', 'test_subtype']);
+  }
+
+  /**
+   * Asserts the call order from an alter call.
+   *
+   * Also asserts additional $type argument values that are meant to produce the
+   * same result.
+   *
+   * @param list<string> $expected
+   *   Expected call list, as strings from __METHOD__ or __FUNCTION__.
+   * @param string|list<string> $type
+   *   First argument to pass to ->alter().
+   */
+  protected function assertAlterCallOrder(array $expected, string|array $type): void {
+    $this->assertSameCallList(
+      $expected,
+      $this->alter($type),
+    );
+  }
+
+  /**
+   * Invokes ModuleHandler->alter() and returns the altered array.
+   *
+   * @param string|list<string> $type
+   *   Alter type or list of alter types.
+   *
+   * @return array
+   *   The altered array.
+   */
+  protected function alter(string|array $type): array {
+    $data = [];
+    \Drupal::moduleHandler()->alter($type, $data);
+    return $data;
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
index 4156481d3b924a88d8fcf1b0953b80e1669b66c2..774f1289ccdb95ec8ccca89a9d45967eac02d6c8 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,150 @@ 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');
+    $module_installer->install(['aaa_hook_collector_test']);
+    $module_installer->install(['bbb_hook_collector_test']);
+    $module_handler = $this->container->get('module_handler');
+    // Last alphabetically uses the Order::First enum to place it before
+    // the implementation it would naturally come after.
+    $expected_calls = [
+      'Drupal\bbb_hook_collector_test\Hook\TestHookFirst::hookFirst',
+      'Drupal\aaa_hook_collector_test\Hook\TestHookFirst::hookFirst',
+    ];
+    $calls = $module_handler->invokeAll('custom_hook_test_hook_first');
+    $this->assertEquals($expected_calls, $calls);
+  }
+
+  /**
+   * Tests hook ordering with attributes.
+   */
+  public function testHookAfter(): void {
+    $module_installer = $this->container->get('module_installer');
+    $module_installer->install(['aaa_hook_collector_test']);
+    $module_installer->install(['bbb_hook_collector_test']);
+    $module_handler = $this->container->get('module_handler');
+    // First alphabetically uses the OrderAfter to place it after
+    // the implementation it would naturally come before.
+    $expected_calls = [
+      'Drupal\bbb_hook_collector_test\Hook\TestHookAfter::hookAfter',
+      'Drupal\aaa_hook_collector_test\Hook\TestHookAfter::hookAfter',
+    ];
+    $calls = $module_handler->invokeAll('custom_hook_test_hook_after');
+    $this->assertEquals($expected_calls, $calls);
+  }
+
+  /**
+   * Tests hook ordering with attributes.
+   */
+  public function testHookAfterClassMethod(): void {
+    $module_installer = $this->container->get('module_installer');
+    $module_installer->install(['aaa_hook_collector_test']);
+    $module_installer->install(['bbb_hook_collector_test']);
+    $module_handler = $this->container->get('module_handler');
+    // First alphabetically uses the OrderAfter to place it after
+    // the implementation it would naturally come before using call and method.
+    $expected_calls = [
+      'Drupal\bbb_hook_collector_test\Hook\TestHookAfterClassMethod::hookAfterClassMethod',
+      'Drupal\aaa_hook_collector_test\Hook\TestHookAfterClassMethod::hookAfterClassMethod',
+    ];
+    $calls = $module_handler->invokeAll('custom_hook_test_hook_after_class_method');
+    $this->assertEquals($expected_calls, $calls);
+  }
+
+  /**
+   * Tests hook ordering with attributes.
+   */
+  public function testHookBefore(): void {
+    $module_installer = $this->container->get('module_installer');
+    $module_installer->install(['aaa_hook_collector_test']);
+    $module_installer->install(['bbb_hook_collector_test']);
+    $module_handler = $this->container->get('module_handler');
+    // First alphabetically uses the OrderBefore to place it before
+    // the implementation it would naturally come after.
+    $expected_calls = [
+      'Drupal\bbb_hook_collector_test\Hook\TestHookBefore::hookBefore',
+      'Drupal\aaa_hook_collector_test\Hook\TestHookBefore::hookBefore',
+    ];
+    $calls = $module_handler->invokeAll('custom_hook_test_hook_before');
+    $this->assertEquals($expected_calls, $calls);
+  }
+
+  /**
+   * Tests hook ordering with attributes.
+   */
+  public function testHookOrderExtraTypes(): void {
+    $module_installer = $this->container->get('module_installer');
+    $module_installer->install(['aaa_hook_collector_test']);
+    $module_installer->install(['bbb_hook_collector_test']);
+    $module_handler = $this->container->get('module_handler');
+    // First alphabetically uses the OrderAfter to place it after
+    // the implementation it would naturally come before.
+    $expected_calls = [
+      'Drupal\bbb_hook_collector_test\Hook\TestHookOrderExtraTypes::customHookExtraTypes',
+      'Drupal\aaa_hook_collector_test\Hook\TestHookOrderExtraTypes::customHookExtraTypes',
+    ];
+    $hooks = [
+      'custom_hook',
+      'custom_hook_extra_types1',
+      'custom_hook_extra_types2',
+    ];
+    $calls = [];
+    $module_handler->alter($hooks, $calls);
+    $this->assertEquals($expected_calls, $calls);
+  }
+
+  /**
+   * Tests hook ordering with attributes.
+   */
+  public function testHookLast(): void {
+    $module_installer = $this->container->get('module_installer');
+    $module_installer->install(['aaa_hook_collector_test']);
+    $module_installer->install(['bbb_hook_collector_test']);
+    $module_handler = $this->container->get('module_handler');
+    // First alphabetically uses the OrderBefore to place it before
+    // the implementation it would naturally come after.
+    $expected_calls = [
+      'Drupal\bbb_hook_collector_test\Hook\TestHookLast::hookLast',
+      'Drupal\aaa_hook_collector_test\Hook\TestHookLast::hookLast',
+    ];
+    $calls = $module_handler->invokeAll('custom_hook_test_hook_last');
+    $this->assertEquals($expected_calls, $calls);
+  }
+
+  /**
+   * Tests hook remove.
+   */
+  public function testHookRemove(): void {
+    $module_installer = $this->container->get('module_installer');
+    $this->assertTrue($module_installer->install(['hook_test_remove']));
+    $module_handler = $this->container->get('module_handler');
+    // There are two hooks implementing custom_hook1.
+    // One is removed with RemoveHook so it should not run.
+    $expected_calls = [
+      'Drupal\hook_test_remove\Hook\TestHookRemove::hookDoRun',
+    ];
+    $calls = $module_handler->invokeAll('custom_hook1');
+    $this->assertEquals($expected_calls, $calls);
+  }
+
+  /**
+   * Tests hook override.
+   */
+  public function testHookOverride(): void {
+    $module_installer = $this->container->get('module_installer');
+    $module_installer->install(['aaa_hook_collector_test']);
+    $module_installer->install(['bbb_hook_collector_test']);
+    $module_handler = $this->container->get('module_handler');
+    $expected_calls = [
+      'Drupal\aaa_hook_collector_test\Hook\TestHookReorderHookFirst::customHookOverride',
+      'Drupal\bbb_hook_collector_test\Hook\TestHookReorderHookLast::customHookOverride',
+    ];
+    $calls = $module_handler->invokeAll('custom_hook_override');
+    $this->assertEquals($expected_calls, $calls);
+  }
+
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Hook/HookOrderTest.php b/core/tests/Drupal/KernelTests/Core/Hook/HookOrderTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..971c95bc8e9ddd46390640a1563ce37e8a350666
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Hook/HookOrderTest.php
@@ -0,0 +1,74 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\KernelTests\Core\Hook;
+
+use Drupal\aaa_hook_order_test\Hook\AHooks;
+use Drupal\bbb_hook_order_test\Hook\BHooks;
+use Drupal\ccc_hook_order_test\Hook\CHooks;
+use Drupal\ddd_hook_order_test\Hook\DHooks;
+use Drupal\KernelTests\KernelTestBase;
+use PHPUnit\Framework\Attributes\IgnoreDeprecations;
+
+/**
+ * @group Hook
+ */
+#[IgnoreDeprecations]
+class HookOrderTest extends KernelTestBase {
+
+  use HookOrderTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'aaa_hook_order_test',
+    'bbb_hook_order_test',
+    'ccc_hook_order_test',
+    'ddd_hook_order_test',
+  ];
+
+  public function testHookOrder(): void {
+    $this->assertSameCallList(
+      [
+        CHooks::class . '::testHookReorderFirst',
+        CHooks::class . '::testHookFirst',
+        AHooks::class . '::testHookFirst',
+        'aaa_hook_order_test_test_hook',
+        AHooks::class . '::testHook',
+        'bbb_hook_order_test_test_hook',
+        BHooks::class . '::testHook',
+        AHooks::class . '::testHookAfterB',
+        'ccc_hook_order_test_test_hook',
+        CHooks::class . '::testHook',
+        'ddd_hook_order_test_test_hook',
+        DHooks::class . '::testHook',
+        AHooks::class . '::testHookLast',
+      ],
+      \Drupal::moduleHandler()->invokeAll('test_hook'),
+    );
+  }
+
+  /**
+   * Tests hook order when each module has either oop or procedural listeners.
+   *
+   * This would detect a possible mistake where we would first collect modules
+   * from all procedural and then from all oop implementations, without fixing
+   * the order.
+   */
+  public function testSparseHookOrder(): void {
+    $this->assertSameCallList(
+      [
+        // OOP and procedural listeners are correctly intermixed by module
+        // order.
+        'aaa_hook_order_test_sparse_test_hook',
+        BHooks::class . '::sparseTestHook',
+        'ccc_hook_order_test_sparse_test_hook',
+        DHooks::class . '::sparseTestHook',
+      ],
+      \Drupal::moduleHandler()->invokeAll('sparse_test_hook'),
+    );
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Hook/HookOrderTestTrait.php b/core/tests/Drupal/KernelTests/Core/Hook/HookOrderTestTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..15238c7b33c7ae0b732fc86e50819cd22f5f3edb
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Hook/HookOrderTestTrait.php
@@ -0,0 +1,56 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\KernelTests\Core\Hook;
+
+/**
+ * @group Hook
+ */
+trait HookOrderTestTrait {
+
+  /**
+   * Asserts that two lists of call strings are the same.
+   *
+   * It is meant for strings produced with __FUNCTION__ or __METHOD__.
+   *
+   * The assertion fails exactly when a regular ->assertSame() would fail, but
+   * it provides a more useful output on failure.
+   *
+   * @param list<string> $expected
+   *   Expected list of strings.
+   * @param list<string> $actual
+   *   Actual list of strings.
+   * @param string $message
+   *   Message to pass to ->assertSame().
+   */
+  protected function assertSameCallList(array $expected, array $actual, string $message = ''): void {
+    // Format without the numeric array keys, but in a way that can be easily
+    // copied into the test.
+    $format = function (array $strings): string {
+      if (!$strings) {
+        return '[]';
+      }
+      $parts = array_map(
+        static function (string $call_string) {
+          if (preg_match('@^(\w+\\\\)*(\w+)::(\w+)@', $call_string, $matches)) {
+            [,, $class_shortname, $method] = $matches;
+            return $class_shortname . '::class . ' . var_export('::' . $method, TRUE);
+          }
+          return var_export($call_string, TRUE);
+        },
+        $strings,
+      );
+      return "[\n  " . implode(",\n  ", $parts) . ",\n]";
+    };
+    $this->assertSame(
+      $format($expected),
+      $format($actual),
+      $message,
+    );
+    // Finally, assert that array keys and the full class names are really the
+    // same, in a way that provides useful output on failure.
+    $this->assertSame($expected, $actual, $message);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php b/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php
index 3b36018806f74ce056163767a47ef712d9d28788..05a064eab7dfc9dae959613390f5275112d6f9c5 100644
--- a/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php
+++ b/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php
@@ -102,6 +102,8 @@ public function testLoadModule(): void {
    * Tests loading all modules.
    *
    * @covers ::loadAll
+   *
+   * @group legacy
    */
   public function testLoadAllModules(): void {
     $moduleList = [
@@ -352,6 +354,8 @@ public function testImplementsHookModuleEnabled(): void {
    * Tests invoke all.
    *
    * @covers ::invokeAll
+   *
+   * @group legacy
    */
   public function testInvokeAll(): void {
     $implementations = [
@@ -382,7 +386,7 @@ function some_method(): void {
 
     };
     $implementations['some_hook'][get_class($c)]['some_method'] = 'some_module';
-    $module_handler = new ModuleHandler($this->root, [], $this->eventDispatcher, $implementations, []);
+    $module_handler = new ModuleHandler($this->root, [], $this->eventDispatcher, $implementations);
     $module_handler->setModuleList(['some_module' => TRUE]);
     $r = new \ReflectionObject($module_handler);
 
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..2d6fba3b451f19a5516a85996d12bc9d7e2f58a3 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;
@@ -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));
-  }
-
 }