From 91748c0cbb73cfa02286f9c9d9ee24286ce044fd Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Mon, 9 Dec 2024 12:23:22 -0500
Subject: [PATCH 001/173] New attributes for ordering

---
 .../Drupal/Core/Hook/Attribute/HookAfter.php  | 36 +++++++++++++++++++
 .../Drupal/Core/Hook/Attribute/HookBefore.php | 36 +++++++++++++++++++
 .../Drupal/Core/Hook/Attribute/HookFirst.php  | 28 +++++++++++++++
 .../Drupal/Core/Hook/Attribute/HookLast.php   | 28 +++++++++++++++
 .../Core/Hook/Attribute/HookOrderGroup.php    | 36 +++++++++++++++++++
 5 files changed, 164 insertions(+)
 create mode 100644 core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
 create mode 100644 core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
 create mode 100644 core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
 create mode 100644 core/lib/Drupal/Core/Hook/Attribute/HookLast.php
 create mode 100644 core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php

diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
new file mode 100644
index 000000000000..dcd282b5b30b
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
@@ -0,0 +1,36 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Attribute;
+
+/**
+ * Attribute for marking that a hook should be changed.
+ *
+ * This allows you to ensure the hook is executed after
+ * a specific hook in another module.
+ *
+ * @section sec_backwards_compatibility Backwards-compatibility
+ *
+ * To allow hook implementations to work on older versions of Drupal as well,
+ * keep the hook_module_implements_alter implementation and add the appropriate
+ * combination of #[HookFirst], #[HookLast], #[HookBefore], #[HookAfter], and
+ * #[HookOrderGroup] attributes. Then ensure you have added #[LegacyHook] to
+ * the hook_module_implements_alter() implementation.
+ *
+ * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
+ */
+#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
+class HookAfter {
+
+  /**
+   * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookAfter.php attribute object.
+   *
+   * @param string $module
+   *   The module this implementation should run before.
+   */
+  public function __construct(
+    public string $module,
+  ) {}
+
+}
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
new file mode 100644
index 000000000000..ba09d8b8d3fb
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
@@ -0,0 +1,36 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Attribute;
+
+/**
+ * Attribute for marking that a hook should be changed.
+ *
+ * This allows you to ensure the hook is executed before
+ * a specific hook in another module.
+ *
+ * @section sec_backwards_compatibility Backwards-compatibility
+ *
+ * To allow hook implementations to work on older versions of Drupal as well,
+ * keep the hook_module_implements_alter implementation and add the appropriate
+ * combination of #[HookFirst], #[HookLast], #[HookBefore], #[HookAfter], and
+ * #[HookOrderGroup] attributes. Then ensure you have added #[LegacyHook] to
+ * the hook_module_implements_alter() implementation.
+ *
+ * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
+ */
+#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
+class HookBefore {
+
+  /**
+   * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookBefore.php attribute object.
+   *
+   * @param string $module
+   *   The module this implementation should run before.
+   */
+  public function __construct(
+    public string $module,
+  ) {}
+
+}
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php b/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
new file mode 100644
index 000000000000..21c33c4cdd6a
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Attribute;
+
+/**
+ * Attribute for marking that a hook should be executed first.
+ *
+ * @section sec_backwards_compatibility Backwards-compatibility
+ *
+ * To allow hook implementations to work on older versions of Drupal as well,
+ * keep the hook_module_implements_alter implementation and add the appropriate
+ * combination of #[HookFirst], #[HookLast], #[HookBefore], #[HookAfter], and
+ * #[HookOrderGroup] attributes. Then ensure you have added #[LegacyHook] to
+ * the hook_module_implements_alter() implementation.
+ *
+ * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
+ */
+#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
+class HookFirst {
+
+  /**
+   * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookFirst.php attribute object.
+   */
+  public function __construct() {}
+
+}
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookLast.php b/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
new file mode 100644
index 000000000000..4269b67c2291
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
@@ -0,0 +1,28 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Attribute;
+
+/**
+ * Attribute for marking that a hook should be executed last.
+ *
+ * @section sec_backwards_compatibility Backwards-compatibility
+ *
+ * To allow hook implementations to work on older versions of Drupal as well,
+ * keep the hook_module_implements_alter implementation and add the appropriate
+ * combination of #[HookFirst], #[HookLast], #[HookBefore], #[HookAfter], and
+ * #[HookOrderGroup] attributes. Then ensure you have added #[LegacyHook] to
+ * the hook_module_implements_alter() implementation.
+ *
+ * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
+ */
+#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
+class HookLast {
+
+  /**
+   * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookLast.php attribute object.
+   */
+  public function __construct() {}
+
+}
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php
new file mode 100644
index 000000000000..0fb0c3bfa183
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php
@@ -0,0 +1,36 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Attribute;
+
+/**
+ * Attribute for marking which specific implementations to group.
+ *
+ * This allows hook ordering to handle extra types such as ordering form_alter
+ * relative to hook_form_FORM_ID_alter.
+ *
+ * @section sec_backwards_compatibility Backwards-compatibility
+ *
+ * To allow hook implementations to work on older versions of Drupal as well,
+ * keep the hook_module_implements_alter implementation and add the appropriate
+ * combination of #[HookFirst], #[HookLast], #[HookBefore], #[HookAfter], and
+ * #[HookOrderGroup] attributes. Then ensure you have added #[LegacyHook] to
+ * the hook_module_implements_alter() implementation.
+ *
+ * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
+ */
+#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
+class HookOrderGroup {
+
+  /**
+   * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookAfter.php attribute object.
+   *
+   * @param array
+   *   The group of implementations to change.
+   */
+  public function __construct(
+    public string $group,
+  ) {}
+
+}
-- 
GitLab


From 101a757d9f3dda4bce37d300e9f97e9f8dd62e6b Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Mon, 9 Dec 2024 13:00:54 -0500
Subject: [PATCH 002/173] Add change priority and grouping WIP

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 120 ++++++++++++++++++
 1 file changed, 120 insertions(+)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 3809e24af21d..bb00f6b44b8f 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -71,6 +71,11 @@ class HookCollectorPass implements CompilerPassInterface {
    */
   private array $groupIncludes = [];
 
+  /**
+   * A list of implementations to reprioritize.
+   */
+  protected array $orderGroup = [];
+
   /**
    * {@inheritdoc}
    */
@@ -311,6 +316,14 @@ protected function addFromAttribute(Hook $hook, $class, $module): void {
     }
     $this->moduleImplements[$hook->hook][$module] = '';
     $this->implementations[$hook->hook][$module][$class][] = $hook->method;
+
+    if ($hook->orderGroup) {
+      if (!isset($this->orderGroup[$hook->orderGroup])) {
+        $this->orderGroup[$hook->orderGroup] = ['drupal_hook' . $hook->orderGroup];
+      }
+      $this->orderGroup[$hook->orderGroup][$hook->hook] = ['drupal_hook' . $hook->hook];
+
+    }
   }
 
   /**
@@ -384,4 +397,111 @@ public static function checkForProceduralOnlyHooks(Hook $hook, string $class): v
     }
   }
 
+    /**
+   * Change the priority of a hook implementation.
+   *
+   * @param \Drupal\Core\DependencyInjection\ContainerBuilder $container
+   *   The container builder.
+   * @param string $hook
+   *   The name of the hook.
+   * @param string $class_and_method
+   *   Class and method separated by :: containing the hook implementation which
+   *   should be changed.
+   * @param bool $should_be_larger
+   *   TRUE for before/first, FALSE for after/last. Larger priority listeners
+   *   fire first.
+   * @param array|null $others
+   *   Other hook implementations to compare to, if any. The array is keyed by
+   *   string containing a class and method separated by ::, the value is not
+   *   used.
+   *
+   * @return void
+   */
+  protected function changePriority(ContainerBuilder $container, string $hook, string $class_and_method, bool $should_be_larger, ?array $others = NULL): void {
+    $events = $this->orderGroup[$hook] ?? ["drupal_hook.$hook"];
+    foreach ($container->findTaggedServiceIds('kernel.event_listener') as $id => $attributes) {
+      foreach ($attributes as $key => $tag) {
+        if (in_array($tag['event'], $events)) {
+          $index = "$id.$key";
+          $priority = $tag['priority'];
+          // Symfony documents event listener priorities to be integers,
+          // HookCollectorPass sets them to be integers, ::setPriority() only
+          // accepts integers.
+          assert(is_int($priority));
+          $priorities[$index] = $priority;
+          $specifier = "$id::" . $tag['method'];
+          if ($class_and_method === $specifier) {
+            $index_this = $index;
+          }
+          // If $others is specified by ::before() / ::after() then for
+          // comparison only the priority of those matter.
+          // For ::first() / ::last() the priority of every other hook
+          // matters.
+          elseif (!isset($others) || isset($others[$specifier])) {
+            $priorities_other[] = $priority;
+          }
+        }
+      }
+    }
+    if (!isset($index_this) || !isset($priorities) || !isset($priorities_other)) {
+      return;
+    }
+    // The priority of the hook being changed.
+    $priority_this = $priorities[$index_this];
+    // The priority of the hook being compared to.
+    $priority_other = $should_be_larger ? max($priorities_other) : min($priorities_other);
+    // If the order is correct there is nothing to do. If the two priorities
+    // are the same then the order is undefined and so it can't be correct.
+    // If they are not the same and $priority_this is already larger exactly
+    // when $should_be_larger says then it's the correct order.
+    if ($priority_this !== $priority_other && ($should_be_larger === ($priority_this > $priority_other))) {
+      return;
+    }
+    $priority_new = $priority_other + ($should_be_larger ? 1 : -1);
+    // For ::first() / ::last() this new priority is already larger/smaller
+    // than all existing priorities but for ::before() / ::after() it might
+    // belong to an already existing hook. In this case set the new priority
+    // temporarily to be halfway between $priority_other and $priority_new
+    // then give all hook implementations new, integer priorities keeping this
+    // new order. This ensures the hook implementation being changed is in the
+    // right order relative to both $priority_other and the hook whose
+    // priority was $priority_new.
+    if (in_array($priority_new, $priorities)) {
+      $priorities[$index_this] = $priority_other + ($should_be_larger ? 0.5 : -0.5);
+      asort($priorities);
+      $changed_indexes = array_keys($priorities);
+      $priorities = array_combine($changed_indexes, range(1, count($changed_indexes)));
+    }
+    else {
+      $priorities[$index_this] = $priority_new;
+      $changed_indexes = [$index_this];
+    }
+    foreach ($changed_indexes as $index) {
+      [$id, $key] = explode('.', $index);
+      self::setPriority($container, $id, (int) $key, $priorities[$index]);
+    }
+  }
+
+  /**
+   * Set the priority of a listener.
+   *
+   * @param \Drupal\Core\DependencyInjection\ContainerBuilder $container
+   *   The container.
+   * @param string $class
+   *   The name of the class, this is the same as the service id.
+   * @param int $key
+   *   The key within the tags array of the 'kernel.event_listener' tag for the
+   *   hook implementation to be changed.
+   * @param int $priority
+   *   The new priority.
+   *
+   * @return void
+   */
+  public static function setPriority(ContainerBuilder $container, string $class, int $key, int $priority): void {
+    $definition = $container->getDefinition($class);
+    $tags = $definition->getTags();
+    $tags['kernel.event_listener'][$key]['priority'] = $priority;
+    $definition->setTags($tags);
+  }
+
 }
-- 
GitLab


From bd996d8ff931d75e104003908c8626d13d7d1eb5 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Mon, 9 Dec 2024 16:31:53 -0500
Subject: [PATCH 003/173] Hook Order

---
 .../Drupal/Core/Hook/Attribute/HookAfter.php  |   4 +-
 .../Drupal/Core/Hook/Attribute/HookBefore.php |   4 +-
 .../Core/Hook/Attribute/HookOrderGroup.php    |   4 +-
 .../Drupal/Core/Hook/HookCollectorPass.php    | 143 +++++++++++-------
 4 files changed, 94 insertions(+), 61 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
index dcd282b5b30b..9763e1cb1fd5 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
@@ -26,11 +26,11 @@ class HookAfter {
   /**
    * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookAfter.php attribute object.
    *
-   * @param string $module
+   * @param array $modules
    *   The module this implementation should run before.
    */
   public function __construct(
-    public string $module,
+    public array $modules,
   ) {}
 
 }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
index ba09d8b8d3fb..73bcf62c3e9e 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
@@ -26,11 +26,11 @@ class HookBefore {
   /**
    * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookBefore.php attribute object.
    *
-   * @param string $module
+   * @param array $modules
    *   The module this implementation should run before.
    */
   public function __construct(
-    public string $module,
+    public array $modules,
   ) {}
 
 }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php
index 0fb0c3bfa183..d011996e6803 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php
@@ -26,11 +26,11 @@ class HookOrderGroup {
   /**
    * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookAfter.php attribute object.
    *
-   * @param array
+   * @param array $group
    *   The group of implementations to change.
    */
   public function __construct(
-    public string $group,
+    public array $group,
   ) {}
 
 }
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index bb00f6b44b8f..89b5d88622e7 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -9,6 +9,11 @@
 use Drupal\Component\FileCache\FileCacheFactory;
 use Drupal\Core\Extension\ProceduralCall;
 use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Attribute\HookAfter;
+use Drupal\Core\Hook\Attribute\HookBefore;
+use Drupal\Core\Hook\Attribute\HookFirst;
+use Drupal\Core\Hook\Attribute\HookLast;
+use Drupal\Core\Hook\Attribute\HookOrderGroup;
 use Drupal\Core\Hook\Attribute\LegacyHook;
 use Drupal\Core\Hook\Attribute\StopProceduralHookScan;
 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
@@ -74,7 +79,12 @@ class HookCollectorPass implements CompilerPassInterface {
   /**
    * A list of implementations to reprioritize.
    */
-  protected array $orderGroup = [];
+  protected array $moduleAttributes = [];
+
+  /**
+   * An organized list of hooks to reorder.
+   */
+  protected array $orderMap = [];
 
   /**
    * {@inheritdoc}
@@ -94,6 +104,42 @@ public function process(ContainerBuilder $container): void {
     }
     $definition = $container->getDefinition('module_handler');
     $definition->setArgument('$groupIncludes', $groupIncludes);
+    foreach ($this->moduleAttributes as $module => $classAttributes) {
+      foreach ($classAttributes as $class => $methodAttributes) {
+        foreach ($methodAttributes as $method => $attributes) {
+          foreach ($attributes as $attribute) {
+            $attribute = $attribute->newInstance();
+            switch (get_class($attribute)) {
+              case Hook::class:
+                self::checkForProceduralOnlyHooks($attribute->hook, $class);
+                $this->addFromAttribute($attribute, $class, $module);
+                break;
+
+              case HookAfter::class:
+                $this->orderMap[$hook][$class][$method]['after'] = $attribute->modules;
+                break;
+
+              case HookBefore::class:
+                $this->orderMap[$hook][$class][$method]['before'] = $attribute->modules;
+                break;
+
+              case HookFirst::class:
+                $this->orderMap[$hook][$class][$method]['first'] = 9999;
+                break;
+
+              case HookLast::class:
+                $this->orderMap[$hook][$class][$method]['last'] = -9999;
+                break;
+
+              case HookOrderGroup::class:
+                $this->orderMap[$hook][$class][$method]['sort'] = $attribute->group;
+                break;
+            }
+          }
+        }
+      }
+    }
+
     foreach ($collector->moduleImplements as $hook => $moduleImplements) {
       foreach ($collector->moduleImplementsAlters as $alter) {
         $alter($moduleImplements, $hook);
@@ -114,13 +160,41 @@ public function process(ContainerBuilder $container): void {
             $definition->addTag('kernel.event_listener', [
               'event' => "drupal_hook.$hook",
               'method' => $method,
-              'priority' => $priority--,
+              'priority' => $priority,
             ]);
           }
         }
       }
     }
     $container->setParameter('hook_implementations_map', $map);
+
+    foreach ($this->orderMap as $hook => $classes) {
+      foreach ($classes as $class => $methods) {
+        foreach ($methods as $method => $actions) {
+          foreach ($actions as $action => $others) {
+            switch ($action) {
+              case 'first':
+                $this->changePriority($container, $hook, "$class::$method", TRUE);
+                break;
+
+              case 'before':
+                // @todo $others likely needs to be updated.
+                $this->changePriority($container, $hook, "$class::$method", TRUE, $others);
+                break;
+
+              case 'after':
+                // @todo $others likely needs to be updated.
+                $this->changePriority($container, $hook, "$class::$method", FALSE, $others);
+                break;
+
+              case 'last':
+                $this->changePriority($container, $hook, "$class::$method", FALSE);
+                break;
+            }
+          }
+        }
+      }
+    }
   }
 
   /**
@@ -174,6 +248,7 @@ public static function collectAllHookImplementations(array $module_filenames, ?C
   protected function collectModuleHookImplementations($dir, $module, $module_preg, bool $skip_procedural): void {
     $hook_file_cache = FileCacheFactory::get('hook_implementations');
     $procedural_hook_file_cache = FileCacheFactory::get('procedural_hook_implementations:' . $module_preg);
+    $this->moduleAttributes[$module] = [];
 
     $iterator = new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::FOLLOW_SYMLINKS);
     $iterator = new \RecursiveCallbackFilterIterator($iterator, static::filterIterator(...));
@@ -192,6 +267,8 @@ protected function collectModuleHookImplementations($dir, $module, $module_preg,
       }
       if ($extension === 'php') {
         $cached = $hook_file_cache->get($filename);
+        // @todo remove this comment.
+        // $cached = FALSE;
         if ($cached) {
           $class = $cached['class'];
           $attributes = $cached['attributes'];
@@ -201,16 +278,19 @@ protected function collectModuleHookImplementations($dir, $module, $module_preg,
           $class = $namespace . '/' . $fileinfo->getBasename('.php');
           $class = str_replace('/', '\\', $class);
           if (class_exists($class)) {
-            $attributes = static::getHookAttributesInClass($class);
+            $reflectionClass = new \ReflectionClass($class);
+            $reflectionClass = new \ReflectionClass($class);
+            $attributes['__invoke'] = $reflectionClass->getAttributes();
+            foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $methodName => $methodReflection) {
+              $attributes[$methodName] = $methodReflection->getAttributes();
+            }
             $hook_file_cache->set($filename, ['class' => $class, 'attributes' => $attributes]);
           }
           else {
             $attributes = [];
           }
         }
-        foreach ($attributes as $attribute) {
-          $this->addFromAttribute($attribute, $class, $module);
-        }
+        $this->moduleAttributes[$module][$class] = array_merge($this->moduleAttributes[$module][$class] ?? [], $attributes);
       }
       elseif (!$skip_procedural) {
         $implementations = $procedural_hook_file_cache->get($filename);
@@ -260,46 +340,6 @@ protected static function filterIterator(\SplFileInfo $fileInfo, $key, \Recursiv
     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.
    *
@@ -316,14 +356,6 @@ protected function addFromAttribute(Hook $hook, $class, $module): void {
     }
     $this->moduleImplements[$hook->hook][$module] = '';
     $this->implementations[$hook->hook][$module][$class][] = $hook->method;
-
-    if ($hook->orderGroup) {
-      if (!isset($this->orderGroup[$hook->orderGroup])) {
-        $this->orderGroup[$hook->orderGroup] = ['drupal_hook' . $hook->orderGroup];
-      }
-      $this->orderGroup[$hook->orderGroup][$hook->hook] = ['drupal_hook' . $hook->hook];
-
-    }
   }
 
   /**
@@ -344,6 +376,7 @@ protected function addProceduralImplementation(\SplFileInfo $fileinfo, string $h
       $this->hookInfo[] = $function;
     }
     if ($hook === 'module_implements_alter') {
+      // @todo confirm this is skipped when #[LegacyHook] should be.
       $this->moduleImplementsAlters[] = $function;
     }
     if ($fileinfo->getExtension() !== 'module') {
@@ -397,7 +430,7 @@ public static function checkForProceduralOnlyHooks(Hook $hook, string $class): v
     }
   }
 
-    /**
+  /**
    * Change the priority of a hook implementation.
    *
    * @param \Drupal\Core\DependencyInjection\ContainerBuilder $container
-- 
GitLab


From fe37d93090a86afe5c6c830051975ef6670f15d1 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Mon, 9 Dec 2024 16:33:41 -0500
Subject: [PATCH 004/173] Fix priority

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 89b5d88622e7..792717eb58de 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -160,7 +160,7 @@ public function process(ContainerBuilder $container): void {
             $definition->addTag('kernel.event_listener', [
               'event' => "drupal_hook.$hook",
               'method' => $method,
-              'priority' => $priority,
+              'priority' => $priority--,
             ]);
           }
         }
-- 
GitLab


From bd57c9ca4d6bc6cd0533c5255a024f065326e29d Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Mon, 9 Dec 2024 16:39:00 -0500
Subject: [PATCH 005/173] change priority

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 792717eb58de..b3523627dad2 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -451,7 +451,9 @@ public static function checkForProceduralOnlyHooks(Hook $hook, string $class): v
    * @return void
    */
   protected function changePriority(ContainerBuilder $container, string $hook, string $class_and_method, bool $should_be_larger, ?array $others = NULL): void {
-    $events = $this->orderGroup[$hook] ?? ["drupal_hook.$hook"];
+    // @todo clean this up.
+    // $events = $this->orderGroup[$hook] ?? ["drupal_hook.$hook"];
+    $events = ["drupal_hook.$hook"];
     foreach ($container->findTaggedServiceIds('kernel.event_listener') as $id => $attributes) {
       foreach ($attributes as $key => $tag) {
         if (in_array($tag['event'], $events)) {
-- 
GitLab


From c585979f6daee949c04db2ae532393c83e4e05ba Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Mon, 9 Dec 2024 16:50:02 -0500
Subject: [PATCH 006/173] Spelling and stan

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 16 +++++-----
 .../Tests/Core/Hook/HookCollectorPassTest.php | 31 -------------------
 2 files changed, 9 insertions(+), 38 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index b3523627dad2..043eb86e1f6c 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -77,7 +77,7 @@ class HookCollectorPass implements CompilerPassInterface {
   private array $groupIncludes = [];
 
   /**
-   * A list of implementations to reprioritize.
+   * A list of attributes in modules for Hooks.
    */
   protected array $moduleAttributes = [];
 
@@ -113,26 +113,27 @@ public function process(ContainerBuilder $container): void {
               case Hook::class:
                 self::checkForProceduralOnlyHooks($attribute->hook, $class);
                 $this->addFromAttribute($attribute, $class, $module);
+                $this->orderMap[$module][$class][$method]['hook'] = $attribute->hook;
                 break;
 
               case HookAfter::class:
-                $this->orderMap[$hook][$class][$method]['after'] = $attribute->modules;
+                $this->orderMap[$module][$class][$method]['after'] = $attribute->modules;
                 break;
 
               case HookBefore::class:
-                $this->orderMap[$hook][$class][$method]['before'] = $attribute->modules;
+                $this->orderMap[$module][$class][$method]['before'] = $attribute->modules;
                 break;
 
               case HookFirst::class:
-                $this->orderMap[$hook][$class][$method]['first'] = 9999;
+                $this->orderMap[$module][$class][$method]['first'] = 9999;
                 break;
 
               case HookLast::class:
-                $this->orderMap[$hook][$class][$method]['last'] = -9999;
+                $this->orderMap[$module][$class][$method]['last'] = -9999;
                 break;
 
               case HookOrderGroup::class:
-                $this->orderMap[$hook][$class][$method]['sort'] = $attribute->group;
+                $this->orderMap[$module][$class][$method]['sort'] = $attribute->group;
                 break;
             }
           }
@@ -168,9 +169,10 @@ public function process(ContainerBuilder $container): void {
     }
     $container->setParameter('hook_implementations_map', $map);
 
-    foreach ($this->orderMap as $hook => $classes) {
+    foreach ($this->orderMap as $module => $classes) {
       foreach ($classes as $class => $methods) {
         foreach ($methods as $method => $actions) {
+          $hook = $this->orderMap[$module][$class][$method]['hook'];
           foreach ($actions as $action => $others) {
             switch ($action) {
               case 'first':
diff --git a/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php
index 4b92b8d6d25f..f7ccd2e17be4 100644
--- a/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php
+++ b/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php
@@ -85,35 +85,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));
-  }
-
 }
-- 
GitLab


From fff37d1810075348d9b24dc71a5d322db4031dc2 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Mon, 9 Dec 2024 16:51:47 -0500
Subject: [PATCH 007/173] PHPCS

---
 core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php | 1 -
 1 file changed, 1 deletion(-)

diff --git a/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php
index f7ccd2e17be4..2d6fba3b451f 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;
-- 
GitLab


From fef41c2e40a1899e161e78dda2919e49d55eeb21 Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Mon, 9 Dec 2024 20:50:12 -0500
Subject: [PATCH 008/173] Fix cache

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 043eb86e1f6c..b691de0c456c 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -281,10 +281,9 @@ protected function collectModuleHookImplementations($dir, $module, $module_preg,
           $class = str_replace('/', '\\', $class);
           if (class_exists($class)) {
             $reflectionClass = new \ReflectionClass($class);
-            $reflectionClass = new \ReflectionClass($class);
-            $attributes['__invoke'] = $reflectionClass->getAttributes();
+            $attributes['__invoke'] = array_map(fn ($x) => $x->newInstance(), $reflectionClass->getAttributes());
             foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $methodName => $methodReflection) {
-              $attributes[$methodName] = $methodReflection->getAttributes();
+              $attributes[$methodName] = array_map(fn ($x) => $x->newInstance(), $methodReflection->getAttributes());
             }
             $hook_file_cache->set($filename, ['class' => $class, 'attributes' => $attributes]);
           }
-- 
GitLab


From ce6556af79de246d8364ace9ca5f045a9bd9702b Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Mon, 9 Dec 2024 22:45:14 -0500
Subject: [PATCH 009/173] Mapping attributes

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 94 +++++++++++--------
 1 file changed, 54 insertions(+), 40 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index b691de0c456c..fa9044bac5a3 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -104,37 +104,47 @@ public function process(ContainerBuilder $container): void {
     }
     $definition = $container->getDefinition('module_handler');
     $definition->setArgument('$groupIncludes', $groupIncludes);
-    foreach ($this->moduleAttributes as $module => $classAttributes) {
-      foreach ($classAttributes as $class => $methodAttributes) {
-        foreach ($methodAttributes as $method => $attributes) {
+    foreach ($collector->moduleAttributes as $module => $classes) {
+      foreach ($classes as $class => $methods) {
+        foreach ($methods as $method => $attributes) {
           foreach ($attributes as $attribute) {
-            $attribute = $attribute->newInstance();
-            switch (get_class($attribute)) {
-              case Hook::class:
-                self::checkForProceduralOnlyHooks($attribute->hook, $class);
-                $this->addFromAttribute($attribute, $class, $module);
-                $this->orderMap[$module][$class][$method]['hook'] = $attribute->hook;
-                break;
-
-              case HookAfter::class:
-                $this->orderMap[$module][$class][$method]['after'] = $attribute->modules;
-                break;
-
-              case HookBefore::class:
-                $this->orderMap[$module][$class][$method]['before'] = $attribute->modules;
-                break;
-
-              case HookFirst::class:
-                $this->orderMap[$module][$class][$method]['first'] = 9999;
-                break;
-
-              case HookLast::class:
-                $this->orderMap[$module][$class][$method]['last'] = -9999;
-                break;
-
-              case HookOrderGroup::class:
-                $this->orderMap[$module][$class][$method]['sort'] = $attribute->group;
-                break;
+            if ($attribute) {
+              switch (get_class($attribute)) {
+                case Hook::class:
+                  $hook = $attribute->hook;
+                  self::checkForProceduralOnlyHooks($attribute, $class);
+                  if ($on_behalf_module = $attribute->module) {
+                    $collector->moduleImplements[$hook][$on_behalf_module] = '';
+                    $collector->implementations[$hook][$on_behalf_module][$class][] = $method;
+                    $collector->orderMap[$on_behalf_module][$class][$method]['hook'] = $hook;
+                  }
+                  else {
+                    $collector->moduleImplements[$hook][$module] = '';
+                    $collector->implementations[$hook][$module][$class][] = $method;
+                    $collector->orderMap[$module][$class][$method]['hook'] = $hook;
+                  }
+                  break;
+
+                case HookAfter::class:
+                  $collector->orderMap[$module][$class][$method]['after'] = $attribute->modules;
+                  break;
+
+                case HookBefore::class:
+                  $collector->orderMap[$module][$class][$method]['before'] = $attribute->modules;
+                  break;
+
+                case HookFirst::class:
+                  $collector->orderMap[$module][$class][$method]['first'] = 9999;
+                  break;
+
+                case HookLast::class:
+                  $collector->orderMap[$module][$class][$method]['last'] = -9999;
+                  break;
+
+                case HookOrderGroup::class:
+                  $collector->orderMap[$module][$class][$method]['sort'] = $attribute->group;
+                  break;
+              }
             }
           }
         }
@@ -147,7 +157,7 @@ public function process(ContainerBuilder $container): void {
       }
       $priority = 0;
       foreach ($moduleImplements as $module => $v) {
-        foreach ($collector->implementations[$hook][$module] as $class => $method_hooks) {
+        foreach ($collector->[$hook][$module] as $class => $method_hooks) {
           if ($container->has($class)) {
             $definition = $container->findDefinition($class);
           }
@@ -169,28 +179,28 @@ public function process(ContainerBuilder $container): void {
     }
     $container->setParameter('hook_implementations_map', $map);
 
-    foreach ($this->orderMap as $module => $classes) {
+    foreach ($collector->orderMap as $module => $classes) {
       foreach ($classes as $class => $methods) {
         foreach ($methods as $method => $actions) {
-          $hook = $this->orderMap[$module][$class][$method]['hook'];
+          $hook = $collector->orderMap[$module][$class][$method]['hook'];
           foreach ($actions as $action => $others) {
             switch ($action) {
               case 'first':
-                $this->changePriority($container, $hook, "$class::$method", TRUE);
+                $collector->changePriority($container, $hook, "$class::$method", TRUE);
                 break;
 
               case 'before':
                 // @todo $others likely needs to be updated.
-                $this->changePriority($container, $hook, "$class::$method", TRUE, $others);
+                $collector->changePriority($container, $hook, "$class::$method", TRUE, $others);
                 break;
 
               case 'after':
                 // @todo $others likely needs to be updated.
-                $this->changePriority($container, $hook, "$class::$method", FALSE, $others);
+                $collector->changePriority($container, $hook, "$class::$method", FALSE, $others);
                 break;
 
               case 'last':
-                $this->changePriority($container, $hook, "$class::$method", FALSE);
+                $collector->changePriority($container, $hook, "$class::$method", FALSE);
                 break;
             }
           }
@@ -281,9 +291,13 @@ protected function collectModuleHookImplementations($dir, $module, $module_preg,
           $class = str_replace('/', '\\', $class);
           if (class_exists($class)) {
             $reflectionClass = new \ReflectionClass($class);
-            $attributes['__invoke'] = array_map(fn ($x) => $x->newInstance(), $reflectionClass->getAttributes());
+            if ($class_attributes = $reflectionClass->getAttributes()) {
+              $attributes['__invoke'] = array_map(fn ($x) => $x->newInstance(), $class_attributes);
+            }
             foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $methodName => $methodReflection) {
-              $attributes[$methodName] = array_map(fn ($x) => $x->newInstance(), $methodReflection->getAttributes());
+              if ($method_attributes = $methodReflection->getAttributes()) {
+                $attributes[$methodReflection->getName()] = array_map(fn ($x) => $x->newInstance(), $method_attributes);
+              }
             }
             $hook_file_cache->set($filename, ['class' => $class, 'attributes' => $attributes]);
           }
@@ -291,7 +305,7 @@ protected function collectModuleHookImplementations($dir, $module, $module_preg,
             $attributes = [];
           }
         }
-        $this->moduleAttributes[$module][$class] = array_merge($this->moduleAttributes[$module][$class] ?? [], $attributes);
+        $this->moduleAttributes[$module][$class] = $attributes;
       }
       elseif (!$skip_procedural) {
         $implementations = $procedural_hook_file_cache->get($filename);
-- 
GitLab


From a55ea567318002d09944c2a4c696d1e1899f5c17 Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Mon, 9 Dec 2024 22:47:28 -0500
Subject: [PATCH 010/173] missed implementations

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index fa9044bac5a3..67ac0f43fc0d 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -157,7 +157,7 @@ public function process(ContainerBuilder $container): void {
       }
       $priority = 0;
       foreach ($moduleImplements as $module => $v) {
-        foreach ($collector->[$hook][$module] as $class => $method_hooks) {
+        foreach ($collector->implementations[$hook][$module] as $class => $method_hooks) {
           if ($container->has($class)) {
             $definition = $container->findDefinition($class);
           }
-- 
GitLab


From 6c8755b480d352aff5a8fce40a6679c9ca402cf7 Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Mon, 9 Dec 2024 22:48:34 -0500
Subject: [PATCH 011/173] CS

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 67ac0f43fc0d..dbe40fd31e2c 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -294,7 +294,7 @@ protected function collectModuleHookImplementations($dir, $module, $module_preg,
             if ($class_attributes = $reflectionClass->getAttributes()) {
               $attributes['__invoke'] = array_map(fn ($x) => $x->newInstance(), $class_attributes);
             }
-            foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $methodName => $methodReflection) {
+            foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $methodReflection) {
               if ($method_attributes = $methodReflection->getAttributes()) {
                 $attributes[$methodReflection->getName()] = array_map(fn ($x) => $x->newInstance(), $method_attributes);
               }
-- 
GitLab


From 70429c6600949dfb2c1a4bc45ac2e60a2d806c6b Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 10 Dec 2024 09:33:04 -0500
Subject: [PATCH 012/173] HookOrderPriority

---
 .../Drupal/Core/Hook/Attribute/HookAfter.php  |   8 +-
 .../Drupal/Core/Hook/Attribute/HookBefore.php |   8 +-
 .../Drupal/Core/Hook/Attribute/HookFirst.php  |   8 +-
 .../Drupal/Core/Hook/Attribute/HookLast.php   |   8 +-
 .../Core/Hook/Attribute/HookOrderGroup.php    |   7 +-
 .../Drupal/Core/Hook/HookCollectorPass.php    | 213 +++---------------
 core/lib/Drupal/Core/Hook/HookPriority.php    | 124 ++++++++++
 core/modules/ckeditor5/ckeditor5.module       |  25 --
 .../ckeditor5/src/Hook/Ckeditor5Hooks.php     |  13 ++
 9 files changed, 202 insertions(+), 212 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Hook/HookPriority.php

diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
index 9763e1cb1fd5..3f630808c0e0 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
@@ -4,6 +4,8 @@
 
 namespace Drupal\Core\Hook\Attribute;
 
+use Drupal\Core\Hook\HookPriority;
+
 /**
  * Attribute for marking that a hook should be changed.
  *
@@ -21,7 +23,7 @@
  * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
-class HookAfter {
+class HookAfter implements HookOrderInterface {
 
   /**
    * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookAfter.php attribute object.
@@ -33,4 +35,8 @@ public function __construct(
     public array $modules,
   ) {}
 
+  public function getOrderAction(string $hook, string $class, string $method): \Closure {
+    return fn(HookPriority $hookPriority) => $hookPriority->change($hook, "$class::$method", FALSE, $this->modules);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
index 73bcf62c3e9e..229598f63173 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
@@ -4,6 +4,8 @@
 
 namespace Drupal\Core\Hook\Attribute;
 
+use Drupal\Core\Hook\HookPriority;
+
 /**
  * Attribute for marking that a hook should be changed.
  *
@@ -21,7 +23,7 @@
  * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
-class HookBefore {
+class HookBefore implements HookOrderInterface {
 
   /**
    * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookBefore.php attribute object.
@@ -33,4 +35,8 @@ public function __construct(
     public array $modules,
   ) {}
 
+  public function getOrderAction(string $hook, string $class, string $method): \Closure {
+    return fn(HookPriority $hookPriority) => $hookPriority->change($hook, "$class::$method", TRUE, $this->modules);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php b/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
index 21c33c4cdd6a..a7957339fb07 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
@@ -4,6 +4,8 @@
 
 namespace Drupal\Core\Hook\Attribute;
 
+use Drupal\Core\Hook\HookPriority;
+
 /**
  * Attribute for marking that a hook should be executed first.
  *
@@ -18,11 +20,15 @@
  * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
-class HookFirst {
+class HookFirst implements HookOrderInterface {
 
   /**
    * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookFirst.php attribute object.
    */
   public function __construct() {}
 
+  public function getOrderAction(string $hook, string $class, string $method): \Closure {
+    return fn(HookPriority $hookPriority) => $hookPriority->change($hook, "$class::$method", TRUE);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookLast.php b/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
index 4269b67c2291..70b21fc530b0 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
@@ -4,6 +4,8 @@
 
 namespace Drupal\Core\Hook\Attribute;
 
+use Drupal\Core\Hook\HookPriority;
+
 /**
  * Attribute for marking that a hook should be executed last.
  *
@@ -18,11 +20,15 @@
  * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
-class HookLast {
+class HookLast implements HookOrderInterface {
 
   /**
    * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookLast.php attribute object.
    */
   public function __construct() {}
 
+  public function getOrderAction(string $hook, string $class, string $method): \Closure {
+    return fn(HookPriority $hookPriority) => $hookPriority->change($hook, "$class::$method", FALSE);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php
index d011996e6803..346a78ee7e67 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php
@@ -24,10 +24,13 @@
 class HookOrderGroup {
 
   /**
-   * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookAfter.php attribute object.
+   * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php attribute object.
    *
    * @param array $group
-   *   The group of implementations to change.
+   *   A list of hooks to sort together. For example, if a method implementing
+   *   form_BASE_FORM_ID_alter wants to sort itself relative to some
+   *   implementations of form_FORM_ID_alter then this would contain those.
+   *   See Ckeditor5::formFilterFormatFormAlter() for example.
    */
   public function __construct(
     public array $group,
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index dbe40fd31e2c..82fbbb7b8200 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -9,11 +9,8 @@
 use Drupal\Component\FileCache\FileCacheFactory;
 use Drupal\Core\Extension\ProceduralCall;
 use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\HookAfter;
-use Drupal\Core\Hook\Attribute\HookBefore;
-use Drupal\Core\Hook\Attribute\HookFirst;
-use Drupal\Core\Hook\Attribute\HookLast;
 use Drupal\Core\Hook\Attribute\HookOrderGroup;
+use Drupal\Core\Hook\Attribute\HookOrderInterface;
 use Drupal\Core\Hook\Attribute\LegacyHook;
 use Drupal\Core\Hook\Attribute\StopProceduralHookScan;
 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
@@ -81,11 +78,6 @@ class HookCollectorPass implements CompilerPassInterface {
    */
   protected array $moduleAttributes = [];
 
-  /**
-   * An organized list of hooks to reorder.
-   */
-  protected array $orderMap = [];
-
   /**
    * {@inheritdoc}
    */
@@ -104,47 +96,39 @@ public function process(ContainerBuilder $container): void {
     }
     $definition = $container->getDefinition('module_handler');
     $definition->setArgument('$groupIncludes', $groupIncludes);
+    $orderGroups = [];
+    /** @var \Closure[] $orderActions */
+    $orderActions = [];
     foreach ($collector->moduleAttributes as $module => $classes) {
       foreach ($classes as $class => $methods) {
         foreach ($methods as $method => $attributes) {
+          $orderAttributes = [];
+          $orderGroup = FALSE;
+          $hook = FALSE;
           foreach ($attributes as $attribute) {
-            if ($attribute) {
-              switch (get_class($attribute)) {
-                case Hook::class:
-                  $hook = $attribute->hook;
-                  self::checkForProceduralOnlyHooks($attribute, $class);
-                  if ($on_behalf_module = $attribute->module) {
-                    $collector->moduleImplements[$hook][$on_behalf_module] = '';
-                    $collector->implementations[$hook][$on_behalf_module][$class][] = $method;
-                    $collector->orderMap[$on_behalf_module][$class][$method]['hook'] = $hook;
-                  }
-                  else {
-                    $collector->moduleImplements[$hook][$module] = '';
-                    $collector->implementations[$hook][$module][$class][] = $method;
-                    $collector->orderMap[$module][$class][$method]['hook'] = $hook;
-                  }
-                  break;
-
-                case HookAfter::class:
-                  $collector->orderMap[$module][$class][$method]['after'] = $attribute->modules;
-                  break;
-
-                case HookBefore::class:
-                  $collector->orderMap[$module][$class][$method]['before'] = $attribute->modules;
-                  break;
-
-                case HookFirst::class:
-                  $collector->orderMap[$module][$class][$method]['first'] = 9999;
-                  break;
-
-                case HookLast::class:
-                  $collector->orderMap[$module][$class][$method]['last'] = -9999;
-                  break;
-
-                case HookOrderGroup::class:
-                  $collector->orderMap[$module][$class][$method]['sort'] = $attribute->group;
-                  break;
+            if ($attribute instanceof Hook) {
+              self::checkForProceduralOnlyHooks($attribute, $class);
+              $hook = $attribute->hook;
+              $hookModule = $attribute->module ?: $module;
+              if ($attribute->method) {
+                $method = $attribute->method;
               }
+              $collector->moduleImplements[$hook][$hookModule] = '';
+              $collector->implementations[$hook][$hookModule][$class][] = $method;
+            }
+            if ($attribute instanceof HookOrderInterface) {
+              $orderAttributes[] = $attribute;
+            }
+            if ($attribute instanceof HookOrderGroup) {
+              $orderGroup = $attribute->group;
+            }
+          }
+          if ($hook) {
+            foreach ($orderAttributes as $orderAttribute) {
+              $orderActions[] = $orderAttribute->getOrderAction($hook, $class, $method);
+            }
+            if ($orderGroup) {
+              $orderGroups[] = array_merge($orderGroup, [$hook]);
             }
           }
         }
@@ -179,33 +163,9 @@ public function process(ContainerBuilder $container): void {
     }
     $container->setParameter('hook_implementations_map', $map);
 
-    foreach ($collector->orderMap as $module => $classes) {
-      foreach ($classes as $class => $methods) {
-        foreach ($methods as $method => $actions) {
-          $hook = $collector->orderMap[$module][$class][$method]['hook'];
-          foreach ($actions as $action => $others) {
-            switch ($action) {
-              case 'first':
-                $collector->changePriority($container, $hook, "$class::$method", TRUE);
-                break;
-
-              case 'before':
-                // @todo $others likely needs to be updated.
-                $collector->changePriority($container, $hook, "$class::$method", TRUE, $others);
-                break;
-
-              case 'after':
-                // @todo $others likely needs to be updated.
-                $collector->changePriority($container, $hook, "$class::$method", FALSE, $others);
-                break;
-
-              case 'last':
-                $collector->changePriority($container, $hook, "$class::$method", FALSE);
-                break;
-            }
-          }
-        }
-      }
+    $hookPriority = new HookPriority($container, $orderGroups);
+    foreach ($orderActions as $orderAction) {
+      $orderAction($hookPriority);
     }
   }
 
@@ -445,113 +405,4 @@ public static function checkForProceduralOnlyHooks(Hook $hook, string $class): v
     }
   }
 
-  /**
-   * Change the priority of a hook implementation.
-   *
-   * @param \Drupal\Core\DependencyInjection\ContainerBuilder $container
-   *   The container builder.
-   * @param string $hook
-   *   The name of the hook.
-   * @param string $class_and_method
-   *   Class and method separated by :: containing the hook implementation which
-   *   should be changed.
-   * @param bool $should_be_larger
-   *   TRUE for before/first, FALSE for after/last. Larger priority listeners
-   *   fire first.
-   * @param array|null $others
-   *   Other hook implementations to compare to, if any. The array is keyed by
-   *   string containing a class and method separated by ::, the value is not
-   *   used.
-   *
-   * @return void
-   */
-  protected function changePriority(ContainerBuilder $container, string $hook, string $class_and_method, bool $should_be_larger, ?array $others = NULL): void {
-    // @todo clean this up.
-    // $events = $this->orderGroup[$hook] ?? ["drupal_hook.$hook"];
-    $events = ["drupal_hook.$hook"];
-    foreach ($container->findTaggedServiceIds('kernel.event_listener') as $id => $attributes) {
-      foreach ($attributes as $key => $tag) {
-        if (in_array($tag['event'], $events)) {
-          $index = "$id.$key";
-          $priority = $tag['priority'];
-          // Symfony documents event listener priorities to be integers,
-          // HookCollectorPass sets them to be integers, ::setPriority() only
-          // accepts integers.
-          assert(is_int($priority));
-          $priorities[$index] = $priority;
-          $specifier = "$id::" . $tag['method'];
-          if ($class_and_method === $specifier) {
-            $index_this = $index;
-          }
-          // If $others is specified by ::before() / ::after() then for
-          // comparison only the priority of those matter.
-          // For ::first() / ::last() the priority of every other hook
-          // matters.
-          elseif (!isset($others) || isset($others[$specifier])) {
-            $priorities_other[] = $priority;
-          }
-        }
-      }
-    }
-    if (!isset($index_this) || !isset($priorities) || !isset($priorities_other)) {
-      return;
-    }
-    // The priority of the hook being changed.
-    $priority_this = $priorities[$index_this];
-    // The priority of the hook being compared to.
-    $priority_other = $should_be_larger ? max($priorities_other) : min($priorities_other);
-    // If the order is correct there is nothing to do. If the two priorities
-    // are the same then the order is undefined and so it can't be correct.
-    // If they are not the same and $priority_this is already larger exactly
-    // when $should_be_larger says then it's the correct order.
-    if ($priority_this !== $priority_other && ($should_be_larger === ($priority_this > $priority_other))) {
-      return;
-    }
-    $priority_new = $priority_other + ($should_be_larger ? 1 : -1);
-    // For ::first() / ::last() this new priority is already larger/smaller
-    // than all existing priorities but for ::before() / ::after() it might
-    // belong to an already existing hook. In this case set the new priority
-    // temporarily to be halfway between $priority_other and $priority_new
-    // then give all hook implementations new, integer priorities keeping this
-    // new order. This ensures the hook implementation being changed is in the
-    // right order relative to both $priority_other and the hook whose
-    // priority was $priority_new.
-    if (in_array($priority_new, $priorities)) {
-      $priorities[$index_this] = $priority_other + ($should_be_larger ? 0.5 : -0.5);
-      asort($priorities);
-      $changed_indexes = array_keys($priorities);
-      $priorities = array_combine($changed_indexes, range(1, count($changed_indexes)));
-    }
-    else {
-      $priorities[$index_this] = $priority_new;
-      $changed_indexes = [$index_this];
-    }
-    foreach ($changed_indexes as $index) {
-      [$id, $key] = explode('.', $index);
-      self::setPriority($container, $id, (int) $key, $priorities[$index]);
-    }
-  }
-
-  /**
-   * Set the priority of a listener.
-   *
-   * @param \Drupal\Core\DependencyInjection\ContainerBuilder $container
-   *   The container.
-   * @param string $class
-   *   The name of the class, this is the same as the service id.
-   * @param int $key
-   *   The key within the tags array of the 'kernel.event_listener' tag for the
-   *   hook implementation to be changed.
-   * @param int $priority
-   *   The new priority.
-   *
-   * @return void
-   */
-  public static function setPriority(ContainerBuilder $container, string $class, int $key, int $priority): void {
-    $definition = $container->getDefinition($class);
-    $tags = $definition->getTags();
-    $tags['kernel.event_listener'][$key]['priority'] = $priority;
-    $definition->setTags($tags);
-  }
-
 }
diff --git a/core/lib/Drupal/Core/Hook/HookPriority.php b/core/lib/Drupal/Core/Hook/HookPriority.php
new file mode 100644
index 000000000000..f34da2273b5e
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/HookPriority.php
@@ -0,0 +1,124 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+
+class HookPriority {
+
+  public function __construct(protected ContainerBuilder $container, protected array $orderGroups) {}
+
+  /**
+   * Change the priority of a hook implementation.
+   *
+   * @param string $hook
+   *   The name of the hook.
+   * @param string $class_and_method
+   *   Class and method separated by :: containing the hook implementation which
+   *   should be changed.
+   * @param bool $should_be_larger
+   *   TRUE for before/first, FALSE for after/last. Larger priority listeners
+   *   fire first.
+   * @param array|null $others
+   *   Other hook implementations to compare to, if any. The array is a list of
+   *   strings containing a class and method separated by ::.
+   *
+   * @return void
+   */
+  public function change(string $hook, string $class_and_method, bool $should_be_larger, ?array $others = NULL): void {
+    $events = ["drupal_hook.$hook"];
+    foreach ($this->orderGroups as $group) {
+      if (in_array($hook, $group)) {
+        foreach ($group as $alsoHook) {
+          $events[] = "drupal_hook.$alsoHook";
+        }
+      }
+    }
+    $events = array_unique($events);
+    foreach ($this->container->findTaggedServiceIds('kernel.event_listener') as $id => $attributes) {
+      foreach ($attributes as $key => $tag) {
+        if (in_array($tag['event'], $events)) {
+          $index = "$id.$key";
+          $priority = $tag['priority'];
+          // Symfony documents event listener priorities to be integers,
+          // HookCollectorPass sets them to be integers, ::setPriority() only
+          // accepts integers.
+          assert(is_int($priority));
+          $priorities[$index] = $priority;
+          $specifier = "$id::" . $tag['method'];
+          if ($class_and_method === $specifier) {
+            $index_this = $index;
+          }
+          // $others is specified for before and after, for these compare only
+          // the priority of those. For first and last the priority of every
+          // other hook matters.
+          elseif (!isset($others) || in_array($specifier, $others)) {
+            $priorities_other[] = $priority;
+          }
+        }
+      }
+    }
+    if (!isset($index_this) || !isset($priorities) || !isset($priorities_other)) {
+      return;
+    }
+    // The priority of the hook being changed.
+    $priority_this = $priorities[$index_this];
+    // The priority of the hook being compared to.
+    $priority_other = $should_be_larger ? max($priorities_other) : min($priorities_other);
+    // If the order is correct there is nothing to do. If the two priorities
+    // are the same then the order is undefined and so it can't be correct.
+    // If they are not the same and $priority_this is already larger exactly
+    // when $should_be_larger says then it's the correct order.
+    if ($priority_this !== $priority_other && ($should_be_larger === ($priority_this > $priority_other))) {
+      return;
+    }
+    $priority_new = $priority_other + ($should_be_larger ? 1 : -1);
+    // For first and last this new priority is already larger/smaller
+    // than all existing priorities but for before / after it might belong to
+    // an already existing hook. In this case set the new priority temporarily
+    // to be halfway between $priority_other and $priority_new then give all
+    // hook implementations new, integer priorities keeping this new order.
+    // This ensures the hook implementation being changed is in the right order
+    // relative to both $priority_other and the hook whose priority was
+    // $priority_new.
+    if (in_array($priority_new, $priorities)) {
+      $priorities[$index_this] = $priority_other + ($should_be_larger ? 0.5 : -0.5);
+      asort($priorities);
+      $changed_indexes = array_keys($priorities);
+      $priorities = array_combine($changed_indexes, range(1, count($changed_indexes)));
+    }
+    else {
+      $priorities[$index_this] = $priority_new;
+      $changed_indexes = [$index_this];
+    }
+    foreach ($changed_indexes as $index) {
+      [$id, $key] = explode('.', $index);
+      self::set($this->container, $id, (int) $key, $priorities[$index]);
+    }
+  }
+
+  /**
+   * Set the priority of a listener.
+   *
+   * @param \Drupal\Core\DependencyInjection\ContainerBuilder $container
+   *   The container.
+   * @param string $class
+   *   The name of the class, this is the same as the service id.
+   * @param int $key
+   *   The key within the tags array of the 'kernel.event_listener' tag for the
+   *   hook implementation to be changed.
+   * @param int $priority
+   *   The new priority.
+   *
+   * @return void
+   */
+  public static function set(ContainerBuilder $container, string $class, int $key, int $priority): void {
+    $definition = $container->getDefinition($class);
+    $tags = $definition->getTags();
+    $tags['kernel.event_listener'][$key]['priority'] = $priority;
+    $definition->setTags($tags);
+  }
+
+}
diff --git a/core/modules/ckeditor5/ckeditor5.module b/core/modules/ckeditor5/ckeditor5.module
index 551ff7f397ea..b8db617e090d 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 85ead6ae51dd..519e23396240 100644
--- a/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php
+++ b/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\ckeditor5\Hook;
 
+use Drupal\Core\Hook\Attribute\HookAfter;
+use Drupal\Core\Hook\Attribute\HookOrderGroup;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Asset\AttachedAssetsInterface;
 use Drupal\Core\Render\Element;
@@ -102,6 +104,17 @@ public function theme() : array {
    * Implements hook_form_FORM_ID_alter().
    */
   #[Hook('form_filter_format_form_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.
+ */
+  #[HookOrderGroup(['form_filter_format_add_form_alter', 'form_filter_format_edit_form_alter'])]
+  #[HookAfter(['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
-- 
GitLab


From 15a6890b565f63c148e1da94f388d67cce31d9eb Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 10 Dec 2024 09:46:38 -0500
Subject: [PATCH 013/173] CS and stan

---
 .../Core/Hook/Attribute/HookOrderInterface.php   | 11 +++++++++++
 .../ckeditor5/src/Hook/Ckeditor5Hooks.php        | 16 +++++++---------
 2 files changed, 18 insertions(+), 9 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php

diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
new file mode 100644
index 000000000000..2fc1b7219115
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
@@ -0,0 +1,11 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Attribute;
+
+interface HookOrderInterface {
+
+  public function getOrderAction(string $hook, string $class, string $method): \Closure;
+
+}
diff --git a/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php b/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php
index 519e23396240..fd6c2dc3cb16 100644
--- a/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php
+++ b/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php
@@ -102,17 +102,15 @@ 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')]
-
-  /**
- * 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.
- */
   #[HookOrderGroup(['form_filter_format_add_form_alter', 'form_filter_format_edit_form_alter'])]
   #[HookAfter(['editor', 'media'])]
   public function formFilterFormatFormAlter(array &$form, FormStateInterface $form_state, $form_id) : void {
-- 
GitLab


From e6df3cda9d1c148b3329d01cd2291420a59347db Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 10 Dec 2024 09:52:37 -0500
Subject: [PATCH 014/173] Stan

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 82fbbb7b8200..7403541e4f98 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -239,8 +239,6 @@ protected function collectModuleHookImplementations($dir, $module, $module_preg,
       }
       if ($extension === 'php') {
         $cached = $hook_file_cache->get($filename);
-        // @todo remove this comment.
-        // $cached = FALSE;
         if ($cached) {
           $class = $cached['class'];
           $attributes = $cached['attributes'];
@@ -249,6 +247,7 @@ 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)) {
             $reflectionClass = new \ReflectionClass($class);
             if ($class_attributes = $reflectionClass->getAttributes()) {
@@ -261,9 +260,6 @@ protected function collectModuleHookImplementations($dir, $module, $module_preg,
             }
             $hook_file_cache->set($filename, ['class' => $class, 'attributes' => $attributes]);
           }
-          else {
-            $attributes = [];
-          }
         }
         $this->moduleAttributes[$module][$class] = $attributes;
       }
@@ -351,7 +347,6 @@ protected function addProceduralImplementation(\SplFileInfo $fileinfo, string $h
       $this->hookInfo[] = $function;
     }
     if ($hook === 'module_implements_alter') {
-      // @todo confirm this is skipped when #[LegacyHook] should be.
       $this->moduleImplementsAlters[] = $function;
     }
     if ($fileinfo->getExtension() !== 'module') {
-- 
GitLab


From 5e11506b9106b1567ff0bd5060c84cad7e5fcd31 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 10 Dec 2024 11:13:37 -0500
Subject: [PATCH 015/173] Procedural hooks

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 41 ++++---------------
 1 file changed, 8 insertions(+), 33 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 7403541e4f98..2fd07bed0d8a 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -38,15 +38,6 @@ class HookCollectorPass implements CompilerPassInterface {
    */
   protected array $implementations = [];
 
-  /**
-   * An associative array of hook implementations.
-   *
-   * Keys are hook, module and an empty string value.
-   *
-   * @see hook_module_implements_alter()
-   */
-  protected array $moduleImplements = [];
-
   /**
    * A list of include files.
    *
@@ -99,21 +90,23 @@ public function process(ContainerBuilder $container): void {
     $orderGroups = [];
     /** @var \Closure[] $orderActions */
     $orderActions = [];
-    foreach ($collector->moduleAttributes as $module => $classes) {
-      foreach ($classes as $class => $methods) {
+    foreach (array_keys($container->getParameter('container.modules')) as $module) {
+      foreach ($collector->moduleAttributes[$module] ?? [] as $class => $methods) {
         foreach ($methods as $method => $attributes) {
           $orderAttributes = [];
           $orderGroup = FALSE;
           $hook = FALSE;
           foreach ($attributes as $attribute) {
             if ($attribute instanceof Hook) {
-              self::checkForProceduralOnlyHooks($attribute, $class);
+              if ($class !== ProceduralCall::class) {
+                self::checkForProceduralOnlyHooks($attribute, $class);
+              }
               $hook = $attribute->hook;
               $hookModule = $attribute->module ?: $module;
               if ($attribute->method) {
                 $method = $attribute->method;
               }
-              $collector->moduleImplements[$hook][$hookModule] = '';
+              $moduleImplements[$hook][$hookModule] = '';
               $collector->implementations[$hook][$hookModule][$class][] = $method;
             }
             if ($attribute instanceof HookOrderInterface) {
@@ -135,7 +128,7 @@ public function process(ContainerBuilder $container): void {
       }
     }
 
-    foreach ($collector->moduleImplements as $hook => $moduleImplements) {
+    foreach ($moduleImplements ?? [] as $hook => $moduleImplements) {
       foreach ($collector->moduleImplementsAlters as $alter) {
         $alter($moduleImplements, $hook);
       }
@@ -311,24 +304,6 @@ protected static function filterIterator(\SplFileInfo $fileInfo, $key, \Recursiv
     return in_array($extension, ['inc', 'module', 'profile', 'install']);
   }
 
-  /**
-   * 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.
    *
@@ -342,7 +317,7 @@ protected function addFromAttribute(Hook $hook, $class, $module): void {
    *   The name of function implementing the hook. (Wow!)
    */
   protected function addProceduralImplementation(\SplFileInfo $fileinfo, string $hook, string $module, string $function): void {
-    $this->addFromAttribute(new Hook($hook, $module . '_' . $hook), ProceduralCall::class, $module);
+    $this->moduleAttributes[$module][ProceduralCall::class][$function] = [new Hook($hook, $module . '_' . $hook)];
     if ($hook === 'hook_info') {
       $this->hookInfo[] = $function;
     }
-- 
GitLab


From 4125e8eb1d992b23f06707f2e1a599bf150df000 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 10 Dec 2024 14:06:04 -0500
Subject: [PATCH 016/173] Fix implements on behalf

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 2fd07bed0d8a..035693858f7f 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -213,7 +213,6 @@ public static function collectAllHookImplementations(array $module_filenames, ?C
   protected function collectModuleHookImplementations($dir, $module, $module_preg, bool $skip_procedural): void {
     $hook_file_cache = FileCacheFactory::get('hook_implementations');
     $procedural_hook_file_cache = FileCacheFactory::get('procedural_hook_implementations:' . $module_preg);
-    $this->moduleAttributes[$module] = [];
 
     $iterator = new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS | \FilesystemIterator::FOLLOW_SYMLINKS);
     $iterator = new \RecursiveCallbackFilterIterator($iterator, static::filterIterator(...));
@@ -308,16 +307,17 @@ protected static function filterIterator(\SplFileInfo $fileInfo, $key, \Recursiv
    * 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?)
-   * @param string $module
-   *   The name of the module. (Truly shocking!)
+   *   The name of the hook.
+   * @param string $hookModule
+   *   The name of the module this hook implementation belongs to. It can be
+   *   different to the file where $function is in.
    * @param string $function
-   *   The name of function implementing the hook. (Wow!)
+   *   The name of function implementing the hook.
    */
-  protected function addProceduralImplementation(\SplFileInfo $fileinfo, string $hook, string $module, string $function): void {
-    $this->moduleAttributes[$module][ProceduralCall::class][$function] = [new Hook($hook, $module . '_' . $hook)];
+  protected function addProceduralImplementation(\SplFileInfo $fileinfo, string $hook, string $hookModule, string $function): void  {
+    $this->moduleAttributes[$hookModule][ProceduralCall::class][$function] = [new Hook($hook, $hookModule . '_' . $hook)];
     if ($hook === 'hook_info') {
       $this->hookInfo[] = $function;
     }
-- 
GitLab


From 587da66b82e83a345c0a6c2a7ba7994db183c49e Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 10 Dec 2024 14:33:26 -0500
Subject: [PATCH 017/173] Clean up service registration and process

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 50 +++++++++++--------
 1 file changed, 30 insertions(+), 20 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 035693858f7f..556203d0eeb0 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -74,19 +74,6 @@ class HookCollectorPass implements CompilerPassInterface {
    */
   public function process(ContainerBuilder $container): void {
     $collector = static::collectAllHookImplementations($container->getParameter('container.modules'), $container);
-    $map = [];
-    $container->register(ProceduralCall::class, ProceduralCall::class)
-      ->addArgument($collector->includes);
-    $groupIncludes = [];
-    foreach ($collector->hookInfo as $function) {
-      foreach ($function() as $hook => $info) {
-        if (isset($collector->groupIncludes[$info['group']])) {
-          $groupIncludes[$hook] = $collector->groupIncludes[$info['group']];
-        }
-      }
-    }
-    $definition = $container->getDefinition('module_handler');
-    $definition->setArgument('$groupIncludes', $groupIncludes);
     $orderGroups = [];
     /** @var \Closure[] $orderActions */
     $orderActions = [];
@@ -128,7 +115,35 @@ public function process(ContainerBuilder $container): void {
       }
     }
 
-    foreach ($moduleImplements ?? [] as $hook => $moduleImplements) {
+    $this->registerServices($container, $collector, $moduleImplements ?? []);
+
+    $hookPriority = new HookPriority($container, $orderGroups);
+    foreach ($orderActions as $orderAction) {
+      $orderAction($hookPriority);
+    }
+  }
+
+  /**
+   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
+   * @param \Drupal\Core\Hook\HookCollectorPass $collector
+   * @param array $allModuleImplements
+   *
+   * @return void
+   */
+  protected function registerServices(ContainerBuilder $container, HookCollectorPass $collector, array $allModuleImplements): void {
+    $container->register(ProceduralCall::class, ProceduralCall::class)
+      ->addArgument($collector->includes);
+    $groupIncludes = [];
+    foreach ($collector->hookInfo as $function) {
+      foreach ($function() as $hook => $info) {
+        if (isset($collector->groupIncludes[$info['group']])) {
+          $groupIncludes[$hook] = $collector->groupIncludes[$info['group']];
+        }
+      }
+    }
+    $definition = $container->getDefinition('module_handler');
+    $definition->setArgument('$groupIncludes', $groupIncludes);
+    foreach ($allModuleImplements as $hook => $moduleImplements) {
       foreach ($collector->moduleImplementsAlters as $alter) {
         $alter($moduleImplements, $hook);
       }
@@ -154,12 +169,7 @@ public function process(ContainerBuilder $container): void {
         }
       }
     }
-    $container->setParameter('hook_implementations_map', $map);
-
-    $hookPriority = new HookPriority($container, $orderGroups);
-    foreach ($orderActions as $orderAction) {
-      $orderAction($hookPriority);
-    }
+    $container->setParameter('hook_implementations_map', $map ?? []);
   }
 
   /**
-- 
GitLab


From 32a97274d5d470f3b8bbb01da8a95dd72a006f19 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 10 Dec 2024 15:19:44 -0500
Subject: [PATCH 018/173] Refactor hook ordering

---
 .../Drupal/Core/Hook/Attribute/HookAfter.php  | 10 ++--
 .../Drupal/Core/Hook/Attribute/HookBefore.php | 10 ++--
 .../Drupal/Core/Hook/Attribute/HookFirst.php  |  9 ++-
 .../Drupal/Core/Hook/Attribute/HookLast.php   |  8 +--
 .../Core/Hook/Attribute/HookOrderBase.php     | 34 +++++++++++
 .../Hook/Attribute/HookOrderInterface.php     |  4 +-
 .../Drupal/Core/Hook/HookCollectorPass.php    | 57 +++++++++++++++----
 core/lib/Drupal/Core/Hook/HookPriority.php    | 22 ++-----
 8 files changed, 105 insertions(+), 49 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php

diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
index 3f630808c0e0..161bdd51c1ca 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
@@ -23,7 +23,7 @@
  * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
-class HookAfter implements HookOrderInterface {
+class HookAfter extends HookOrderBase {
 
   /**
    * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookAfter.php attribute object.
@@ -32,11 +32,9 @@ class HookAfter implements HookOrderInterface {
    *   The module this implementation should run before.
    */
   public function __construct(
-    public array $modules,
-  ) {}
-
-  public function getOrderAction(string $hook, string $class, string $method): \Closure {
-    return fn(HookPriority $hookPriority) => $hookPriority->change($hook, "$class::$method", FALSE, $this->modules);
+    public readonly array $modules,
+  ) {
+    parent::__construct(FALSE);
   }
 
 }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
index 229598f63173..1fb10ba31258 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
@@ -23,7 +23,7 @@
  * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
-class HookBefore implements HookOrderInterface {
+class HookBefore extends HookOrderBase {
 
   /**
    * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookBefore.php attribute object.
@@ -32,11 +32,9 @@ class HookBefore implements HookOrderInterface {
    *   The module this implementation should run before.
    */
   public function __construct(
-    public array $modules,
-  ) {}
-
-  public function getOrderAction(string $hook, string $class, string $method): \Closure {
-    return fn(HookPriority $hookPriority) => $hookPriority->change($hook, "$class::$method", TRUE, $this->modules);
+    public readonly array $modules,
+  ) {
+    parent::__construct(TRUE);
   }
 
 }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php b/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
index a7957339fb07..f6597196e793 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
@@ -20,15 +20,14 @@
  * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
-class HookFirst implements HookOrderInterface {
+class HookFirst extends HookOrderBase {
 
   /**
    * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookFirst.php attribute object.
    */
-  public function __construct() {}
-
-  public function getOrderAction(string $hook, string $class, string $method): \Closure {
-    return fn(HookPriority $hookPriority) => $hookPriority->change($hook, "$class::$method", TRUE);
+  public function __construct() {
+    parent::__construct(TRUE);
   }
 
+
 }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookLast.php b/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
index 70b21fc530b0..eeca6e3e8c00 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
@@ -20,15 +20,13 @@
  * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
-class HookLast implements HookOrderInterface {
+class HookLast extends HookOrderBase {
 
   /**
    * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookLast.php attribute object.
    */
-  public function __construct() {}
-
-  public function getOrderAction(string $hook, string $class, string $method): \Closure {
-    return fn(HookPriority $hookPriority) => $hookPriority->change($hook, "$class::$method", FALSE);
+  public function __construct() {
+    parent::__construct(FALSE);
   }
 
 }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
new file mode 100644
index 000000000000..d17417dfcbf1
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
@@ -0,0 +1,34 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Attribute;
+
+class HookOrderBase implements HookOrderInterface {
+
+  public readonly string $hook;
+
+  public readonly string $class;
+
+  public readonly string $method;
+
+  public function __construct(public readonly bool $shouldBeLarger) {
+
+  }
+
+  public function setHook(string $hook): static {
+    $this->hook = $hook;
+    return $this;
+  }
+
+  public function setClass(string $class): static {
+    $this->class = $class;
+    return $this;
+  }
+
+  public function setMethod(string $method): static {
+    $this->method = $method;
+    return $this;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
index 2fc1b7219115..5080620d959c 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
@@ -6,6 +6,8 @@
 
 interface HookOrderInterface {
 
-  public function getOrderAction(string $hook, string $class, string $method): \Closure;
+  public function setHook(string $hook): static;
+  public function setClass(string $class): static;
+  public function setMethod(string $method): static;
 
 }
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 556203d0eeb0..591618310116 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -75,8 +75,8 @@ class HookCollectorPass implements CompilerPassInterface {
   public function process(ContainerBuilder $container): void {
     $collector = static::collectAllHookImplementations($container->getParameter('container.modules'), $container);
     $orderGroups = [];
-    /** @var \Closure[] $orderActions */
-    $orderActions = [];
+    /** @var \Drupal\Core\Hook\Attribute\HookOrderBase[] $allOrderAttributes */
+    $allOrderAttributes = [];
     foreach (array_keys($container->getParameter('container.modules')) as $module) {
       foreach ($collector->moduleAttributes[$module] ?? [] as $class => $methods) {
         foreach ($methods as $method => $attributes) {
@@ -105,7 +105,10 @@ public function process(ContainerBuilder $container): void {
           }
           if ($hook) {
             foreach ($orderAttributes as $orderAttribute) {
-              $orderActions[] = $orderAttribute->getOrderAction($hook, $class, $method);
+              $allOrderAttributes[] = $orderAttribute
+                ->setHook($hook)
+                ->setClass($class)
+                ->setMethod($method);
             }
             if ($orderGroup) {
               $orderGroups[] = array_merge($orderGroup, [$hook]);
@@ -115,12 +118,8 @@ public function process(ContainerBuilder $container): void {
       }
     }
 
-    $this->registerServices($container, $collector, $moduleImplements ?? []);
-
-    $hookPriority = new HookPriority($container, $orderGroups);
-    foreach ($orderActions as $orderAction) {
-      $orderAction($hookPriority);
-    }
+    static::registerServices($container, $collector, $moduleImplements ?? []);
+    static::reOrderServices($container, $allOrderAttributes, $orderGroups, $collector->implementations);
   }
 
   /**
@@ -130,7 +129,7 @@ public function process(ContainerBuilder $container): void {
    *
    * @return void
    */
-  protected function registerServices(ContainerBuilder $container, HookCollectorPass $collector, array $allModuleImplements): void {
+  protected static function registerServices(ContainerBuilder $container, HookCollectorPass $collector, array $allModuleImplements): void {
     $container->register(ProceduralCall::class, ProceduralCall::class)
       ->addArgument($collector->includes);
     $groupIncludes = [];
@@ -172,6 +171,44 @@ protected function registerServices(ContainerBuilder $container, HookCollectorPa
     $container->setParameter('hook_implementations_map', $map ?? []);
   }
 
+  /**
+   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
+   * @param array $allOrderAttributes
+   * @param array $orderGroups
+   * @param array $implementations
+   *
+   * @return void
+   */
+  protected static function reOrderServices(ContainerBuilder $container, array $allOrderAttributes, array $orderGroups, array $implementations): void {
+    $hookPriority = new HookPriority($container);
+    foreach ($allOrderAttributes as $orderAttribute) {
+      $hooks = [$orderAttribute->hook];
+      foreach ($orderGroups as $group) {
+        if (in_array($orderAttribute->hook, $group)) {
+          $hooks = array_merge($hooks, $group);
+        }
+      }
+      $hooks = array_unique($hooks);
+      if (isset($orderAttribute->modules)) {
+        $others = [];
+        foreach ($orderAttribute->modules as $module) {
+          foreach ($hooks as $hook) {
+            foreach ($implementations[$hook][$module] as $class => $methods) {
+              foreach ($methods as $method) {
+                $others[] = "$class::$method";
+              }
+            }
+          }
+        }
+      }
+      else {
+        $others = NULL;
+      }
+      $hookPriority->change($hooks, "$orderAttribute->class::$orderAttribute->method", $orderAttribute->shouldBeLarger, $others);
+    }
+  }
+
+
   /**
    * Collects all hook implementations.
    *
diff --git a/core/lib/Drupal/Core/Hook/HookPriority.php b/core/lib/Drupal/Core/Hook/HookPriority.php
index f34da2273b5e..82033db888d8 100644
--- a/core/lib/Drupal/Core/Hook/HookPriority.php
+++ b/core/lib/Drupal/Core/Hook/HookPriority.php
@@ -8,7 +8,7 @@
 
 class HookPriority {
 
-  public function __construct(protected ContainerBuilder $container, protected array $orderGroups) {}
+  public function __construct(protected ContainerBuilder $container) {}
 
   /**
    * Change the priority of a hook implementation.
@@ -27,16 +27,8 @@ public function __construct(protected ContainerBuilder $container, protected arr
    *
    * @return void
    */
-  public function change(string $hook, string $class_and_method, bool $should_be_larger, ?array $others = NULL): void {
-    $events = ["drupal_hook.$hook"];
-    foreach ($this->orderGroups as $group) {
-      if (in_array($hook, $group)) {
-        foreach ($group as $alsoHook) {
-          $events[] = "drupal_hook.$alsoHook";
-        }
-      }
-    }
-    $events = array_unique($events);
+  public function change(array $hooks, string $class_and_method, bool $should_be_larger, ?array $others = NULL): void {
+    $events = array_map(fn ($hook) => "drupal_hook.$hook", $hooks);
     foreach ($this->container->findTaggedServiceIds('kernel.event_listener') as $id => $attributes) {
       foreach ($attributes as $key => $tag) {
         if (in_array($tag['event'], $events)) {
@@ -95,15 +87,13 @@ public function change(string $hook, string $class_and_method, bool $should_be_l
     }
     foreach ($changed_indexes as $index) {
       [$id, $key] = explode('.', $index);
-      self::set($this->container, $id, (int) $key, $priorities[$index]);
+      $this->set($id, (int) $key, $priorities[$index]);
     }
   }
 
   /**
    * Set the priority of a listener.
    *
-   * @param \Drupal\Core\DependencyInjection\ContainerBuilder $container
-   *   The container.
    * @param string $class
    *   The name of the class, this is the same as the service id.
    * @param int $key
@@ -114,8 +104,8 @@ public function change(string $hook, string $class_and_method, bool $should_be_l
    *
    * @return void
    */
-  public static function set(ContainerBuilder $container, string $class, int $key, int $priority): void {
-    $definition = $container->getDefinition($class);
+  public function set(string $class, int $key, int $priority): void {
+    $definition = $this->container->getDefinition($class);
     $tags = $definition->getTags();
     $tags['kernel.event_listener'][$key]['priority'] = $priority;
     $definition->setTags($tags);
-- 
GitLab


From ea185669afec851f860c02d6d0e2e4c2c1c42ce9 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 10 Dec 2024 15:31:24 -0500
Subject: [PATCH 019/173] Fix cs

---
 core/lib/Drupal/Core/Hook/Attribute/HookAfter.php          | 4 ++--
 core/lib/Drupal/Core/Hook/Attribute/HookBefore.php         | 4 ++--
 core/lib/Drupal/Core/Hook/Attribute/HookFirst.php          | 5 ++---
 core/lib/Drupal/Core/Hook/Attribute/HookLast.php           | 4 ++--
 core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php | 2 ++
 core/lib/Drupal/Core/Hook/HookCollectorPass.php            | 1 -
 6 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
index 161bdd51c1ca..86af0eb1806d 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
@@ -4,7 +4,7 @@
 
 namespace Drupal\Core\Hook\Attribute;
 
-use Drupal\Core\Hook\HookPriority;
+use Drupal\Core\Hook\Attribute\HookOrderBase;
 
 /**
  * Attribute for marking that a hook should be changed.
@@ -26,7 +26,7 @@
 class HookAfter extends HookOrderBase {
 
   /**
-   * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookAfter.php attribute object.
+   * Constructs a HookAfter attribute.
    *
    * @param array $modules
    *   The module this implementation should run before.
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
index 1fb10ba31258..5fbde2e1356d 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
@@ -4,7 +4,7 @@
 
 namespace Drupal\Core\Hook\Attribute;
 
-use Drupal\Core\Hook\HookPriority;
+use Drupal\Core\Hook\Attribute\HookOrderBase;
 
 /**
  * Attribute for marking that a hook should be changed.
@@ -26,7 +26,7 @@
 class HookBefore extends HookOrderBase {
 
   /**
-   * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookBefore.php attribute object.
+   * Constructs a HookBefore attribute.
    *
    * @param array $modules
    *   The module this implementation should run before.
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php b/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
index f6597196e793..36b8d21edd41 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
@@ -4,7 +4,7 @@
 
 namespace Drupal\Core\Hook\Attribute;
 
-use Drupal\Core\Hook\HookPriority;
+use Drupal\Core\Hook\Attribute\HookOrderBase;
 
 /**
  * Attribute for marking that a hook should be executed first.
@@ -23,11 +23,10 @@
 class HookFirst extends HookOrderBase {
 
   /**
-   * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookFirst.php attribute object.
+   * Constructs a HookFirst attribute.
    */
   public function __construct() {
     parent::__construct(TRUE);
   }
 
-
 }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookLast.php b/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
index eeca6e3e8c00..ae509b890b94 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
@@ -4,7 +4,7 @@
 
 namespace Drupal\Core\Hook\Attribute;
 
-use Drupal\Core\Hook\HookPriority;
+use Drupal\Core\Hook\Attribute\HookOrderBase;
 
 /**
  * Attribute for marking that a hook should be executed last.
@@ -23,7 +23,7 @@
 class HookLast extends HookOrderBase {
 
   /**
-   * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookLast.php attribute object.
+   * Constructs a HookLast attribute.
    */
   public function __construct() {
     parent::__construct(FALSE);
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
index 5080620d959c..c5406a63801e 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
@@ -7,7 +7,9 @@
 interface HookOrderInterface {
 
   public function setHook(string $hook): static;
+
   public function setClass(string $class): static;
+
   public function setMethod(string $method): static;
 
 }
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 591618310116..1b3e52035ffb 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -208,7 +208,6 @@ protected static function reOrderServices(ContainerBuilder $container, array $al
     }
   }
 
-
   /**
    * Collects all hook implementations.
    *
-- 
GitLab


From 91011303b80f0734e4c9a72e06075acd524ad770 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 10 Dec 2024 15:33:06 -0500
Subject: [PATCH 020/173] Fix comments

---
 core/lib/Drupal/Core/Hook/Attribute/HookBefore.php | 2 +-
 core/lib/Drupal/Core/Hook/HookCollectorPass.php    | 3 +++
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
index 5fbde2e1356d..8d244c11d2f3 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
@@ -26,7 +26,7 @@
 class HookBefore extends HookOrderBase {
 
   /**
-   * Constructs a HookBefore attribute.
+   * Constructs a HookBefore lib/Drupal/Core/Hook/Attribute/attribute.
    *
    * @param array $modules
    *   The module this implementation should run before.
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 1b3e52035ffb..0e0f2b1d8074 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -124,8 +124,11 @@ public function process(ContainerBuilder $container): void {
 
   /**
    * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
+   *   The container.
    * @param \Drupal\Core\Hook\HookCollectorPass $collector
+   *   The collector.
    * @param array $allModuleImplements
+   *   Modules that implement hooks.
    *
    * @return void
    */
-- 
GitLab


From 9912a06da0010c491a94d870551682b5ae3b0b75 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 10 Dec 2024 15:56:53 -0500
Subject: [PATCH 021/173] Internal

---
 .../Core/Hook/Attribute/HookOrderBase.php     | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
index d17417dfcbf1..752fcea02c71 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
@@ -6,11 +6,20 @@
 
 class HookOrderBase implements HookOrderInterface {
 
-  public readonly string $hook;
-
-  public readonly string $class;
-
-  public readonly string $method;
+  /**
+   * @internal
+   */
+  public string $hook;
+
+  /**
+   * @internal
+   */
+  public string $class;
+
+  /**
+   * @internal
+   */
+  public string $method;
 
   public function __construct(public readonly bool $shouldBeLarger) {
 
-- 
GitLab


From 740220c596e077d3d9597175aa9437e4ba0cc87b Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 10 Dec 2024 16:18:54 -0500
Subject: [PATCH 022/173] Ordering tests

---
 .../hook_order_first_alphabetically1.info.yml |  6 +++
 .../src/Hook/FirstAlphabeticallyHooks1.php    | 35 ++++++++++++++
 .../hook_order_first_alphabetically2.info.yml |  6 +++
 .../src/Hook/FirstAlphabeticallyHooks2.php    | 36 +++++++++++++++
 .../hook_order_last_alphabetically1.info.yml  |  6 +++
 .../src/Hook/LastAlphabeticallyHooks1.php     | 46 +++++++++++++++++++
 .../hook_order_last_alphabetically2.info.yml  |  6 +++
 .../src/Hook/LastAlphabeticallyHooks2.php     | 38 +++++++++++++++
 .../Core/Hook/HookCollectorPassTest.php       | 38 +++++++++++++++
 9 files changed, 217 insertions(+)
 create mode 100644 core/modules/system/tests/modules/hook_order_first_alphabetically1/hook_order_first_alphabetically1.info.yml
 create mode 100644 core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php
 create mode 100644 core/modules/system/tests/modules/hook_order_first_alphabetically2/hook_order_first_alphabetically2.info.yml
 create mode 100644 core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php
 create mode 100644 core/modules/system/tests/modules/hook_order_last_alphabetically1/hook_order_last_alphabetically1.info.yml
 create mode 100644 core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php
 create mode 100644 core/modules/system/tests/modules/hook_order_last_alphabetically2/hook_order_last_alphabetically2.info.yml
 create mode 100644 core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php

diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically1/hook_order_first_alphabetically1.info.yml b/core/modules/system/tests/modules/hook_order_first_alphabetically1/hook_order_first_alphabetically1.info.yml
new file mode 100644
index 000000000000..d61a2ddc1212
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically1/hook_order_first_alphabetically1.info.yml
@@ -0,0 +1,6 @@
+name: first alphabetically
+type: module
+description: 'Test module used to test hook ordering.'
+package: Testing
+version: VERSION
+hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php b/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php
new file mode 100644
index 000000000000..142c377d686c
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php
@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_first_alphabetically1\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Attribute\HookLast;
+
+/**
+ * Hook implementations for hook_order_first_alphabetically.
+ */
+class FirstAlphabeticallyHooks1 {
+
+  /**
+   * After LastAlphabeticallyHooks1::cacheFlush1
+   */
+  #[Hook('cache_flush')]
+  public static function cacheFlush1(): void {
+    if(!isset($GLOBALS['HookFirst'])) {
+      $GLOBALS['HookOutOfOrderTestingFirst'] = 'HookOutOfOrderTestingFirst';
+    }
+    $GLOBALS['HookRanTestingFirst'] = 'HookRanTestingFirst';
+  }
+
+  /**
+   * After LastAlphabeticallyHooks1::cacheFlush2
+   */
+  #[HookLast]
+  #[Hook('cache_flush')]
+  public static function cacheFlush2(): void {
+    $GLOBALS['HookLast'] = 'HookLast';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically2/hook_order_first_alphabetically2.info.yml b/core/modules/system/tests/modules/hook_order_first_alphabetically2/hook_order_first_alphabetically2.info.yml
new file mode 100644
index 000000000000..d61a2ddc1212
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically2/hook_order_first_alphabetically2.info.yml
@@ -0,0 +1,6 @@
+name: first alphabetically
+type: module
+description: 'Test module used to test hook ordering.'
+package: Testing
+version: VERSION
+hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php b/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php
new file mode 100644
index 000000000000..14ff7cf0c961
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php
@@ -0,0 +1,36 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_first_alphabetically2\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Attribute\HookAfter;
+use Drupal\Core\Hook\Attribute\HookOrderGroup;
+
+/**
+ * Hook implementations for hook_order_first_alphabetically.
+ */
+class FirstAlphabeticallyHooks2 {
+  /**
+   * After LastAlphabeticallyHooks2::cacheFlush1
+   */
+  #[HookAfter(['hook_order_last_alphabetically2'])]
+  #[HookOrderGroup(['cacheFlush1'])]
+  #[Hook('cache_flush')]
+  public static function cacheFlush1(): void {
+    $GLOBALS['HookAfter'] = 'HookAfter';
+  }
+
+  /**
+   * After LastAlphabeticallyHooks2::cacheFlush2
+   */
+  #[Hook('cache_flush')]
+  public static function cacheFlush2(): void {
+    if(!isset($GLOBALS['HookBefore'])) {
+      $GLOBALS['HookOutOfOrderTestingBefore'] = 'HookOutOfOrderTestingBefore';
+    }
+    $GLOBALS['HookRanTestingBefore'] = 'HookRanTestingBefore';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically1/hook_order_last_alphabetically1.info.yml b/core/modules/system/tests/modules/hook_order_last_alphabetically1/hook_order_last_alphabetically1.info.yml
new file mode 100644
index 000000000000..59334b38d2fc
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically1/hook_order_last_alphabetically1.info.yml
@@ -0,0 +1,6 @@
+name: Hook ordering last
+type: module
+description: 'Test module used to test hook ordering.'
+package: Testing
+version: VERSION
+hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php b/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php
new file mode 100644
index 000000000000..bd19a4b7e1b1
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php
@@ -0,0 +1,46 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_last_alphabetically1\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Attribute\HookFirst;
+
+/**
+ * Hook implementations for hook_order_last_alphabetically.
+ */
+class LastAlphabeticallyHooks1 {
+
+  /**
+   * Before FirstAlphabeticallyHooks1::cacheFlush1
+   */
+  #[HookFirst]
+  #[Hook('cache_flush')]
+  public static function cacheFlush1(): void {
+    $GLOBALS['HookFirst'] = 'HookFirst';
+  }
+
+  /**
+   * Before FirstAlphabeticallyHooks1::cacheFlush2
+   */
+  #[Hook('cache_flush')]
+  public static function cacheFlush2(): void {
+    if(isset($GLOBALS['HookLast'])) {
+      $GLOBALS['HookOutOfOrderTestingLast'] = 'HookOutOfOrderTestingLast';
+    }
+    $GLOBALS['HookRanTestingLast'] = 'HookRanTestingLast';
+  }
+
+  /**
+   * Before FirstAlphabeticallyHooks::cacheFlush3
+   */
+  #[Hook('cache_flush')]
+  public static function cacheFlush3(): void {
+    if(isset($GLOBALS['HookLast'])) {
+      $GLOBALS['HookOutOfOrderTestingLast'] = 'HookOutOfOrderTestingLast';
+    }
+    $GLOBALS['HookRanTestingLast'] = 'HookRanTestingLast';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically2/hook_order_last_alphabetically2.info.yml b/core/modules/system/tests/modules/hook_order_last_alphabetically2/hook_order_last_alphabetically2.info.yml
new file mode 100644
index 000000000000..59334b38d2fc
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically2/hook_order_last_alphabetically2.info.yml
@@ -0,0 +1,6 @@
+name: Hook ordering last
+type: module
+description: 'Test module used to test hook ordering.'
+package: Testing
+version: VERSION
+hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php b/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php
new file mode 100644
index 000000000000..1cc7ac3768a6
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php
@@ -0,0 +1,38 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_last_alphabetically2\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Attribute\HookBefore;
+use Drupal\Core\Hook\Attribute\HookOrderGroup;
+
+/**
+ * Hook implementations for hook_order_last_alphabetically.
+ */
+class LastAlphabeticallyHooks2 {
+
+  /**
+   * Before FirstAlphabeticallyHooks2::cacheFlush1
+   */
+  #[Hook('cache_flush')]
+  public static function cacheFlush1(): void {
+    // This should be run before so HookAfter should not be set.
+    if(isset($GLOBALS['HookAfter'])) {
+      $GLOBALS['HookOutOfOrderTestingAfter'] = 'HookOutOfOrderTestingAfter';
+    }
+    $GLOBALS['HookRanTestingAfter'] = 'HookRanTestingAfter';
+  }
+
+  /**
+   * Before FirstAlphabeticallyHooks2::cacheFlush2
+   */
+  #[HookBefore(['hook_order_last_alphabetically2'])]
+  #[HookOrderGroup(['cacheFlush2'])]
+  #[Hook('cache_flush')]
+  public static function cacheFlush2(): void {
+    $GLOBALS['HookBefore'] = 'HookBefore';
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
index 4156481d3b92..2f0832a8f83d 100644
--- a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
@@ -121,7 +121,45 @@ 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']));
+  }
 
+  /**
+   * Tests HookFirst.
+   */
+  public function testHookFirst(): void {
+    $module_installer = $this->container->get('module_installer');
+    $module_handler = $this->container->get('module_handler');
+    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically1']));
+    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically1']));
+    $this->assertFalse(isset($GLOBALS['HookFirst']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingFirst']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingFirst']));
+    $this->assertFalse(isset($GLOBALS['HookLast']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingLast']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingLast']));
+    drupal_flush_all_caches();
+    $this->assertTrue(isset($GLOBALS['HookFirst']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingFirst']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingFirst']));
+    $this->assertTrue(isset($GLOBALS['HookLast']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingLast']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingLast']));
+
+    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically2']));
+    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically2']));
+    $this->assertFalse(isset($GLOBALS['HookAfter']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingAfter']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingAfter']));
+    $this->assertFalse(isset($GLOBALS['HookBefore']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingBefore']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingBefore']));
+    drupal_flush_all_caches();
+    $this->assertTrue(isset($GLOBALS['HookAfter']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingAfter']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingAfter']));
+    $this->assertTrue(isset($GLOBALS['HookBefore']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingBefore']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingBefore']));
   }
 
   /**
-- 
GitLab


From 47af2a6f1e383ad7e86d96a87720451eca6cfcac Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 10 Dec 2024 16:23:02 -0500
Subject: [PATCH 023/173] CS and fix group

---
 core/lib/Drupal/Core/Hook/Attribute/HookAfter.php      |  2 --
 core/lib/Drupal/Core/Hook/Attribute/HookBefore.php     |  2 --
 core/lib/Drupal/Core/Hook/Attribute/HookFirst.php      |  2 --
 core/lib/Drupal/Core/Hook/Attribute/HookLast.php       |  2 --
 .../src/Hook/FirstAlphabeticallyHooks1.php             |  6 +++---
 .../src/Hook/FirstAlphabeticallyHooks2.php             |  9 +++++----
 .../src/Hook/LastAlphabeticallyHooks1.php              | 10 +++++-----
 .../src/Hook/LastAlphabeticallyHooks2.php              |  8 ++++----
 8 files changed, 17 insertions(+), 24 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
index 86af0eb1806d..c1ee29d48ad9 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
@@ -4,8 +4,6 @@
 
 namespace Drupal\Core\Hook\Attribute;
 
-use Drupal\Core\Hook\Attribute\HookOrderBase;
-
 /**
  * Attribute for marking that a hook should be changed.
  *
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
index 8d244c11d2f3..cdbef7f0e983 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
@@ -4,8 +4,6 @@
 
 namespace Drupal\Core\Hook\Attribute;
 
-use Drupal\Core\Hook\Attribute\HookOrderBase;
-
 /**
  * Attribute for marking that a hook should be changed.
  *
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php b/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
index 36b8d21edd41..774d74f0bb29 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
@@ -4,8 +4,6 @@
 
 namespace Drupal\Core\Hook\Attribute;
 
-use Drupal\Core\Hook\Attribute\HookOrderBase;
-
 /**
  * Attribute for marking that a hook should be executed first.
  *
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookLast.php b/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
index ae509b890b94..2f23ef11c055 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
@@ -4,8 +4,6 @@
 
 namespace Drupal\Core\Hook\Attribute;
 
-use Drupal\Core\Hook\Attribute\HookOrderBase;
-
 /**
  * Attribute for marking that a hook should be executed last.
  *
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php b/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php
index 142c377d686c..d9e190560719 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php
@@ -13,18 +13,18 @@
 class FirstAlphabeticallyHooks1 {
 
   /**
-   * After LastAlphabeticallyHooks1::cacheFlush1
+   * After LastAlphabeticallyHooks1::cacheFlush1.
    */
   #[Hook('cache_flush')]
   public static function cacheFlush1(): void {
-    if(!isset($GLOBALS['HookFirst'])) {
+    if (!isset($GLOBALS['HookFirst'])) {
       $GLOBALS['HookOutOfOrderTestingFirst'] = 'HookOutOfOrderTestingFirst';
     }
     $GLOBALS['HookRanTestingFirst'] = 'HookRanTestingFirst';
   }
 
   /**
-   * After LastAlphabeticallyHooks1::cacheFlush2
+   * After LastAlphabeticallyHooks1::cacheFlush2.
    */
   #[HookLast]
   #[Hook('cache_flush')]
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php b/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php
index 14ff7cf0c961..97e6124acae7 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php
@@ -12,22 +12,23 @@
  * Hook implementations for hook_order_first_alphabetically.
  */
 class FirstAlphabeticallyHooks2 {
+
   /**
-   * After LastAlphabeticallyHooks2::cacheFlush1
+   * After LastAlphabeticallyHooks2::cacheFlush1.
    */
   #[HookAfter(['hook_order_last_alphabetically2'])]
-  #[HookOrderGroup(['cacheFlush1'])]
+  #[HookOrderGroup(['cache_flush'])]
   #[Hook('cache_flush')]
   public static function cacheFlush1(): void {
     $GLOBALS['HookAfter'] = 'HookAfter';
   }
 
   /**
-   * After LastAlphabeticallyHooks2::cacheFlush2
+   * After LastAlphabeticallyHooks2::cacheFlush2.
    */
   #[Hook('cache_flush')]
   public static function cacheFlush2(): void {
-    if(!isset($GLOBALS['HookBefore'])) {
+    if (!isset($GLOBALS['HookBefore'])) {
       $GLOBALS['HookOutOfOrderTestingBefore'] = 'HookOutOfOrderTestingBefore';
     }
     $GLOBALS['HookRanTestingBefore'] = 'HookRanTestingBefore';
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php b/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php
index bd19a4b7e1b1..c9b15dc39f21 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php
@@ -13,7 +13,7 @@
 class LastAlphabeticallyHooks1 {
 
   /**
-   * Before FirstAlphabeticallyHooks1::cacheFlush1
+   * Before FirstAlphabeticallyHooks1::cacheFlush1.
    */
   #[HookFirst]
   #[Hook('cache_flush')]
@@ -22,22 +22,22 @@ public static function cacheFlush1(): void {
   }
 
   /**
-   * Before FirstAlphabeticallyHooks1::cacheFlush2
+   * Before FirstAlphabeticallyHooks1::cacheFlush2.
    */
   #[Hook('cache_flush')]
   public static function cacheFlush2(): void {
-    if(isset($GLOBALS['HookLast'])) {
+    if (isset($GLOBALS['HookLast'])) {
       $GLOBALS['HookOutOfOrderTestingLast'] = 'HookOutOfOrderTestingLast';
     }
     $GLOBALS['HookRanTestingLast'] = 'HookRanTestingLast';
   }
 
   /**
-   * Before FirstAlphabeticallyHooks::cacheFlush3
+   * Before FirstAlphabeticallyHooks::cacheFlush3.
    */
   #[Hook('cache_flush')]
   public static function cacheFlush3(): void {
-    if(isset($GLOBALS['HookLast'])) {
+    if (isset($GLOBALS['HookLast'])) {
       $GLOBALS['HookOutOfOrderTestingLast'] = 'HookOutOfOrderTestingLast';
     }
     $GLOBALS['HookRanTestingLast'] = 'HookRanTestingLast';
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php b/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php
index 1cc7ac3768a6..c0161c2a239d 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php
@@ -14,22 +14,22 @@
 class LastAlphabeticallyHooks2 {
 
   /**
-   * Before FirstAlphabeticallyHooks2::cacheFlush1
+   * Before FirstAlphabeticallyHooks2::cacheFlush1.
    */
   #[Hook('cache_flush')]
   public static function cacheFlush1(): void {
     // This should be run before so HookAfter should not be set.
-    if(isset($GLOBALS['HookAfter'])) {
+    if (isset($GLOBALS['HookAfter'])) {
       $GLOBALS['HookOutOfOrderTestingAfter'] = 'HookOutOfOrderTestingAfter';
     }
     $GLOBALS['HookRanTestingAfter'] = 'HookRanTestingAfter';
   }
 
   /**
-   * Before FirstAlphabeticallyHooks2::cacheFlush2
+   * Before FirstAlphabeticallyHooks2::cacheFlush2.
    */
   #[HookBefore(['hook_order_last_alphabetically2'])]
-  #[HookOrderGroup(['cacheFlush2'])]
+  #[HookOrderGroup(['cache_flush'])]
   #[Hook('cache_flush')]
   public static function cacheFlush2(): void {
     $GLOBALS['HookBefore'] = 'HookBefore';
-- 
GitLab


From 01df6a7cf8f922d19b70b1cfca15c126476745c1 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 10 Dec 2024 16:28:27 -0500
Subject: [PATCH 024/173] CS

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 4 ++++
 core/lib/Drupal/Core/Hook/HookPriority.php      | 2 +-
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 0e0f2b1d8074..09456a4f8fed 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -176,9 +176,13 @@ protected static function registerServices(ContainerBuilder $container, HookColl
 
   /**
    * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
+   *   The container.
    * @param array $allOrderAttributes
+   *   All attributes related to ordering.
    * @param array $orderGroups
+   *   Groups to order by.
    * @param array $implementations
+   *   Hook implementations.
    *
    * @return void
    */
diff --git a/core/lib/Drupal/Core/Hook/HookPriority.php b/core/lib/Drupal/Core/Hook/HookPriority.php
index 82033db888d8..d0b909b3b60a 100644
--- a/core/lib/Drupal/Core/Hook/HookPriority.php
+++ b/core/lib/Drupal/Core/Hook/HookPriority.php
@@ -13,7 +13,7 @@ public function __construct(protected ContainerBuilder $container) {}
   /**
    * Change the priority of a hook implementation.
    *
-   * @param string $hook
+   * @param array $hooks
    *   The name of the hook.
    * @param string $class_and_method
    *   Class and method separated by :: containing the hook implementation which
-- 
GitLab


From 8b397a173a6bcba1049c80b5ad4e71156c0291ef Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 10 Dec 2024 16:55:20 -0500
Subject: [PATCH 025/173] Fix missing array key

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 09456a4f8fed..940f518b6699 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -200,7 +200,7 @@ protected static function reOrderServices(ContainerBuilder $container, array $al
         $others = [];
         foreach ($orderAttribute->modules as $module) {
           foreach ($hooks as $hook) {
-            foreach ($implementations[$hook][$module] as $class => $methods) {
+            foreach ($implementations[$hook][$module] ?? [] as $class => $methods) {
               foreach ($methods as $method) {
                 $others[] = "$class::$method";
               }
-- 
GitLab


From 300b6bb376fbea2fd32ffb841239bbad1da4fa94 Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Tue, 10 Dec 2024 21:00:09 -0500
Subject: [PATCH 026/173] Split before and after into two modules

---
 .../src/Hook/FirstAlphabeticallyHooks2.php    | 11 --------
 .../hook_order_first_alphabetically3.info.yml |  6 +++++
 .../src/Hook/FirstAlphabeticallyHooks3.php    | 27 +++++++++++++++++++
 .../src/Hook/LastAlphabeticallyHooks1.php     | 11 --------
 .../src/Hook/LastAlphabeticallyHooks2.php     | 10 -------
 .../hook_order_last_alphabetically3.info.yml  |  6 +++++
 .../src/Hook/LastAlphabeticallyHooks3.php     | 26 ++++++++++++++++++
 .../Core/Hook/HookCollectorPassTest.php       | 11 +++++---
 8 files changed, 73 insertions(+), 35 deletions(-)
 create mode 100644 core/modules/system/tests/modules/hook_order_first_alphabetically3/hook_order_first_alphabetically3.info.yml
 create mode 100644 core/modules/system/tests/modules/hook_order_first_alphabetically3/src/Hook/FirstAlphabeticallyHooks3.php
 create mode 100644 core/modules/system/tests/modules/hook_order_last_alphabetically3/hook_order_last_alphabetically3.info.yml
 create mode 100644 core/modules/system/tests/modules/hook_order_last_alphabetically3/src/Hook/LastAlphabeticallyHooks3.php

diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php b/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php
index 97e6124acae7..920a685d5f67 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php
@@ -23,15 +23,4 @@ public static function cacheFlush1(): void {
     $GLOBALS['HookAfter'] = 'HookAfter';
   }
 
-  /**
-   * After LastAlphabeticallyHooks2::cacheFlush2.
-   */
-  #[Hook('cache_flush')]
-  public static function cacheFlush2(): void {
-    if (!isset($GLOBALS['HookBefore'])) {
-      $GLOBALS['HookOutOfOrderTestingBefore'] = 'HookOutOfOrderTestingBefore';
-    }
-    $GLOBALS['HookRanTestingBefore'] = 'HookRanTestingBefore';
-  }
-
 }
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically3/hook_order_first_alphabetically3.info.yml b/core/modules/system/tests/modules/hook_order_first_alphabetically3/hook_order_first_alphabetically3.info.yml
new file mode 100644
index 000000000000..d61a2ddc1212
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically3/hook_order_first_alphabetically3.info.yml
@@ -0,0 +1,6 @@
+name: first alphabetically
+type: module
+description: 'Test module used to test hook ordering.'
+package: Testing
+version: VERSION
+hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically3/src/Hook/FirstAlphabeticallyHooks3.php b/core/modules/system/tests/modules/hook_order_first_alphabetically3/src/Hook/FirstAlphabeticallyHooks3.php
new file mode 100644
index 000000000000..24355e8a3362
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically3/src/Hook/FirstAlphabeticallyHooks3.php
@@ -0,0 +1,27 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_first_alphabetically3\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Attribute\HookAfter;
+use Drupal\Core\Hook\Attribute\HookOrderGroup;
+
+/**
+ * Hook implementations for hook_order_first_alphabetically.
+ */
+class FirstAlphabeticallyHooks3 {
+
+  /**
+   * After LastAlphabeticallyHooks3::cacheFlush.
+   */
+  #[Hook('cache_flush')]
+  public static function cacheFlush(): void {
+    if (!isset($GLOBALS['HookBefore'])) {
+      $GLOBALS['HookOutOfOrderTestingBefore'] = 'HookOutOfOrderTestingBefore';
+    }
+    $GLOBALS['HookRanTestingBefore'] = 'HookRanTestingBefore';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php b/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php
index c9b15dc39f21..a90d8e6b67fb 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php
@@ -32,15 +32,4 @@ public static function cacheFlush2(): void {
     $GLOBALS['HookRanTestingLast'] = 'HookRanTestingLast';
   }
 
-  /**
-   * Before FirstAlphabeticallyHooks::cacheFlush3.
-   */
-  #[Hook('cache_flush')]
-  public static function cacheFlush3(): void {
-    if (isset($GLOBALS['HookLast'])) {
-      $GLOBALS['HookOutOfOrderTestingLast'] = 'HookOutOfOrderTestingLast';
-    }
-    $GLOBALS['HookRanTestingLast'] = 'HookRanTestingLast';
-  }
-
 }
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php b/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php
index c0161c2a239d..73375ea550c8 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php
@@ -25,14 +25,4 @@ public static function cacheFlush1(): void {
     $GLOBALS['HookRanTestingAfter'] = 'HookRanTestingAfter';
   }
 
-  /**
-   * Before FirstAlphabeticallyHooks2::cacheFlush2.
-   */
-  #[HookBefore(['hook_order_last_alphabetically2'])]
-  #[HookOrderGroup(['cache_flush'])]
-  #[Hook('cache_flush')]
-  public static function cacheFlush2(): void {
-    $GLOBALS['HookBefore'] = 'HookBefore';
-  }
-
 }
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically3/hook_order_last_alphabetically3.info.yml b/core/modules/system/tests/modules/hook_order_last_alphabetically3/hook_order_last_alphabetically3.info.yml
new file mode 100644
index 000000000000..59334b38d2fc
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically3/hook_order_last_alphabetically3.info.yml
@@ -0,0 +1,6 @@
+name: Hook ordering last
+type: module
+description: 'Test module used to test hook ordering.'
+package: Testing
+version: VERSION
+hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically3/src/Hook/LastAlphabeticallyHooks3.php b/core/modules/system/tests/modules/hook_order_last_alphabetically3/src/Hook/LastAlphabeticallyHooks3.php
new file mode 100644
index 000000000000..16d1177ee0f9
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically3/src/Hook/LastAlphabeticallyHooks3.php
@@ -0,0 +1,26 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_last_alphabetically3\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Attribute\HookBefore;
+use Drupal\Core\Hook\Attribute\HookOrderGroup;
+
+/**
+ * Hook implementations for hook_order_last_alphabetically3.
+ */
+class LastAlphabeticallyHooks3 {
+
+  /**
+   * Before FirstAlphabeticallyHooks3::cacheFlush.
+   */
+  #[HookBefore(['hook_order_last_alphabetically3'])]
+  #[HookOrderGroup(['cache_flush'])]
+  #[Hook('cache_flush')]
+  public static function cacheFlush(): void {
+    $GLOBALS['HookBefore'] = 'HookBefore';
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
index 2f0832a8f83d..0f8450cff86b 100644
--- a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
@@ -150,13 +150,18 @@ public function testHookFirst(): void {
     $this->assertFalse(isset($GLOBALS['HookAfter']));
     $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingAfter']));
     $this->assertFalse(isset($GLOBALS['HookRanTestingAfter']));
-    $this->assertFalse(isset($GLOBALS['HookBefore']));
-    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingBefore']));
-    $this->assertFalse(isset($GLOBALS['HookRanTestingBefore']));
+
     drupal_flush_all_caches();
     $this->assertTrue(isset($GLOBALS['HookAfter']));
     $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingAfter']));
     $this->assertTrue(isset($GLOBALS['HookRanTestingAfter']));
+
+    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically3']));
+    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically3']));
+    $this->assertFalse(isset($GLOBALS['HookBefore']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingBefore']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingBefore']));
+    drupal_flush_all_caches();
     $this->assertTrue(isset($GLOBALS['HookBefore']));
     $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingBefore']));
     $this->assertTrue(isset($GLOBALS['HookRanTestingBefore']));
-- 
GitLab


From ca6e75f282e7e6de5a31ca5ede0405fab5e88960 Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Tue, 10 Dec 2024 21:22:42 -0500
Subject: [PATCH 027/173] Fix cs and the test

---
 .../src/Hook/FirstAlphabeticallyHooks3.php                      | 2 --
 .../src/Hook/LastAlphabeticallyHooks2.php                       | 2 --
 .../src/Hook/LastAlphabeticallyHooks3.php                       | 2 +-
 .../Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php      | 1 -
 4 files changed, 1 insertion(+), 6 deletions(-)

diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically3/src/Hook/FirstAlphabeticallyHooks3.php b/core/modules/system/tests/modules/hook_order_first_alphabetically3/src/Hook/FirstAlphabeticallyHooks3.php
index 24355e8a3362..dc84d9c8fef2 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically3/src/Hook/FirstAlphabeticallyHooks3.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically3/src/Hook/FirstAlphabeticallyHooks3.php
@@ -5,8 +5,6 @@
 namespace Drupal\hook_order_first_alphabetically3\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\HookAfter;
-use Drupal\Core\Hook\Attribute\HookOrderGroup;
 
 /**
  * Hook implementations for hook_order_first_alphabetically.
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php b/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php
index 73375ea550c8..b95ceb3a8531 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php
@@ -5,8 +5,6 @@
 namespace Drupal\hook_order_last_alphabetically2\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\HookBefore;
-use Drupal\Core\Hook\Attribute\HookOrderGroup;
 
 /**
  * Hook implementations for hook_order_last_alphabetically.
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically3/src/Hook/LastAlphabeticallyHooks3.php b/core/modules/system/tests/modules/hook_order_last_alphabetically3/src/Hook/LastAlphabeticallyHooks3.php
index 16d1177ee0f9..3f985067ba9b 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically3/src/Hook/LastAlphabeticallyHooks3.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically3/src/Hook/LastAlphabeticallyHooks3.php
@@ -16,7 +16,7 @@ class LastAlphabeticallyHooks3 {
   /**
    * Before FirstAlphabeticallyHooks3::cacheFlush.
    */
-  #[HookBefore(['hook_order_last_alphabetically3'])]
+  #[HookBefore(['hook_order_first_alphabetically3'])]
   #[HookOrderGroup(['cache_flush'])]
   #[Hook('cache_flush')]
   public static function cacheFlush(): void {
diff --git a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
index 0f8450cff86b..28b38812974c 100644
--- a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
@@ -150,7 +150,6 @@ public function testHookFirst(): void {
     $this->assertFalse(isset($GLOBALS['HookAfter']));
     $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingAfter']));
     $this->assertFalse(isset($GLOBALS['HookRanTestingAfter']));
-
     drupal_flush_all_caches();
     $this->assertTrue(isset($GLOBALS['HookAfter']));
     $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingAfter']));
-- 
GitLab


From e9eb762c4e35eca15b7b4e44e694c9b1852b769d Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Wed, 11 Dec 2024 09:24:16 -0500
Subject: [PATCH 028/173] Patience

---
 .../tests/src/Nightwatch/Tests/ckEditor5CodeSyntaxTest.js        | 1 +
 1 file changed, 1 insertion(+)

diff --git a/core/modules/ckeditor5/tests/src/Nightwatch/Tests/ckEditor5CodeSyntaxTest.js b/core/modules/ckeditor5/tests/src/Nightwatch/Tests/ckEditor5CodeSyntaxTest.js
index 2bf3247862c7..5dd7d76aa87e 100644
--- a/core/modules/ckeditor5/tests/src/Nightwatch/Tests/ckEditor5CodeSyntaxTest.js
+++ b/core/modules/ckeditor5/tests/src/Nightwatch/Tests/ckEditor5CodeSyntaxTest.js
@@ -43,6 +43,7 @@ module.exports = {
         // Wait for new source editing vertical tab to be present before continuing.
         .waitForElementVisible(
           '[href*=edit-editor-settings-plugins-ckeditor5-sourceediting]',
+          9000
         )
         .click('.ckeditor5-toolbar-item-codeBlock') // Select the Code Block button.
         // Hit the down arrow key to move it to the toolbar.
-- 
GitLab


From 22fad5ba626d5bae7b793cd71eb1c5578bfc2555 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Wed, 11 Dec 2024 09:32:10 -0500
Subject: [PATCH 029/173] eslint

---
 .../tests/src/Nightwatch/Tests/ckEditor5CodeSyntaxTest.js       | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/modules/ckeditor5/tests/src/Nightwatch/Tests/ckEditor5CodeSyntaxTest.js b/core/modules/ckeditor5/tests/src/Nightwatch/Tests/ckEditor5CodeSyntaxTest.js
index 5dd7d76aa87e..42cc0a335f90 100644
--- a/core/modules/ckeditor5/tests/src/Nightwatch/Tests/ckEditor5CodeSyntaxTest.js
+++ b/core/modules/ckeditor5/tests/src/Nightwatch/Tests/ckEditor5CodeSyntaxTest.js
@@ -43,7 +43,7 @@ module.exports = {
         // Wait for new source editing vertical tab to be present before continuing.
         .waitForElementVisible(
           '[href*=edit-editor-settings-plugins-ckeditor5-sourceediting]',
-          9000
+          9000,
         )
         .click('.ckeditor5-toolbar-item-codeBlock') // Select the Code Block button.
         // Hit the down arrow key to move it to the toolbar.
-- 
GitLab


From f9df346aef1afd21d1337ca18b45a27767fe17b8 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Wed, 11 Dec 2024 09:40:37 -0500
Subject: [PATCH 030/173] Fix modulehandler add

---
 .../Drupal/Core/Extension/ModuleHandler.php   |  5 ++--
 .../Drupal/Core/Hook/HookCollectorPass.php    | 26 +++++++++----------
 2 files changed, 16 insertions(+), 15 deletions(-)

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index cf42c65deeff..1c38979c78e3 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -192,7 +192,8 @@ protected function add($type, $name, $path) {
     $filename = file_exists($php_file_path) ? "$name.$type" : NULL;
     $this->moduleList[$name] = new Extension($this->root, $type, $pathname, $filename);
     $this->resetImplementations();
-    $hook_collector = HookCollectorPass::collectAllHookImplementations([$name => ['pathname' => $pathname]]);
+    $paths = [$name => ['pathname' => $pathname]];
+    $hook_collector = HookCollectorPass::collectAllHookImplementations($paths);
     // A module freshly added will not be registered on the container yet.
     // ProceduralCall service does not yet know about it.
     // Note in HookCollectorPass:
@@ -200,7 +201,7 @@ protected function add($type, $name, $path) {
     // Load all includes so the legacy section of invoke can handle hooks in includes.
     $hook_collector->loadAllIncludes();
     // Register procedural implementations.
-    foreach ($hook_collector->getImplementations() as $hook => $moduleImplements) {
+    foreach ($hook_collector->getImplementations($paths) as $hook => $moduleImplements) {
       foreach ($moduleImplements as $module => $classImplements) {
         foreach ($classImplements[ProceduralCall::class] ?? [] as $method) {
           $this->invokeMap[$hook][$module][] = $method;
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 940f518b6699..8ee3f08f8ee5 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -31,13 +31,6 @@
  */
 class HookCollectorPass implements CompilerPassInterface {
 
-  /**
-   * An associative array of hook implementations.
-   *
-   * Keys are hook, module, class. Values are a list of methods.
-   */
-  protected array $implementations = [];
-
   /**
    * A list of include files.
    *
@@ -72,8 +65,9 @@ class HookCollectorPass implements CompilerPassInterface {
   /**
    * {@inheritdoc}
    */
-  public function process(ContainerBuilder $container): void {
+  public function process(ContainerBuilder $container): array {
     $collector = static::collectAllHookImplementations($container->getParameter('container.modules'), $container);
+    $implementations = [];
     $orderGroups = [];
     /** @var \Drupal\Core\Hook\Attribute\HookOrderBase[] $allOrderAttributes */
     $allOrderAttributes = [];
@@ -94,7 +88,7 @@ public function process(ContainerBuilder $container): void {
                 $method = $attribute->method;
               }
               $moduleImplements[$hook][$hookModule] = '';
-              $collector->implementations[$hook][$hookModule][$class][] = $method;
+              $implementations[$hook][$hookModule][$class][] = $method;
             }
             if ($attribute instanceof HookOrderInterface) {
               $orderAttributes[] = $attribute;
@@ -118,8 +112,12 @@ public function process(ContainerBuilder $container): void {
       }
     }
 
-    static::registerServices($container, $collector, $moduleImplements ?? []);
-    static::reOrderServices($container, $allOrderAttributes, $orderGroups, $collector->implementations);
+    // This can be removed when ModuleHandler::add() is removed.
+    if ($container->hasDefinition('module_handler')) {
+      static::registerServices($container, $collector, $moduleImplements ?? []);
+      static::reOrderServices($container, $allOrderAttributes, $orderGroups, $collector->implementations);
+    }
+    return $implementations;
   }
 
   /**
@@ -398,8 +396,10 @@ public function loadAllIncludes(): void {
    *
    * @internal
    */
-  public function getImplementations(): array {
-    return $this->implementations;
+  public function getImplementations($paths): array {
+    $container = new ContainerBuilder();
+    $container->setParameter('container.modules', $paths);
+    return $this->process($container);
   }
 
   /**
-- 
GitLab


From 435cd2f3fea3dfe5049e21de260d0a8ce76c12c8 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Wed, 11 Dec 2024 09:56:34 -0500
Subject: [PATCH 031/173] Add implementations back in

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 8ee3f08f8ee5..f33fe4a4e601 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -114,8 +114,8 @@ public function process(ContainerBuilder $container): array {
 
     // This can be removed when ModuleHandler::add() is removed.
     if ($container->hasDefinition('module_handler')) {
-      static::registerServices($container, $collector, $moduleImplements ?? []);
-      static::reOrderServices($container, $allOrderAttributes, $orderGroups, $collector->implementations);
+      static::registerServices($container, $collector, $implementations, $moduleImplements ?? []);
+      static::reOrderServices($container, $allOrderAttributes, $orderGroups, $implementations);
     }
     return $implementations;
   }
@@ -125,12 +125,14 @@ public function process(ContainerBuilder $container): array {
    *   The container.
    * @param \Drupal\Core\Hook\HookCollectorPass $collector
    *   The collector.
+   * @param array $implementations
+   *   All implementations.
    * @param array $allModuleImplements
    *   Modules that implement hooks.
    *
    * @return void
    */
-  protected static function registerServices(ContainerBuilder $container, HookCollectorPass $collector, array $allModuleImplements): void {
+  protected static function registerServices(ContainerBuilder $container, HookCollectorPass $collector, array $implementations, array $allModuleImplements): void {
     $container->register(ProceduralCall::class, ProceduralCall::class)
       ->addArgument($collector->includes);
     $groupIncludes = [];
@@ -149,7 +151,7 @@ protected static function registerServices(ContainerBuilder $container, HookColl
       }
       $priority = 0;
       foreach ($moduleImplements as $module => $v) {
-        foreach ($collector->implementations[$hook][$module] as $class => $method_hooks) {
+        foreach ($implementations[$hook][$module] as $class => $method_hooks) {
           if ($container->has($class)) {
             $definition = $container->findDefinition($class);
           }
-- 
GitLab


From 267be4bd5822206c0266113cd9511070e9a8db57 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Wed, 11 Dec 2024 13:01:47 -0500
Subject: [PATCH 032/173] Fix ckeditor5

---
 .../Drupal/Core/Extension/ModuleHandler.php   | 31 ++-----------------
 .../Drupal/Core/Hook/HookCollectorPass.php    |  2 +-
 2 files changed, 3 insertions(+), 30 deletions(-)

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 1c38979c78e3..283f04c1b981 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -447,9 +447,7 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
       // 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;
@@ -461,32 +459,6 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
     }
   }
 
-  /**
-   * Reorder modules for alters.
-   *
-   * @param array $modules
-   *   A list of modules.
-   * @param string $hook
-   *   The hook being worked on, for example form_alter.
-   *
-   * @return array
-   *   The list, potentially reordered and changed by
-   *   hook_module_implements_alter().
-   */
-  protected function reOrderModulesForAlter(array $modules, string $hook): array {
-    // Order by module order first.
-    $modules = array_intersect(array_keys($this->moduleList), $modules);
-    // Alter expects the module list to be in the keys.
-    $implementations = array_fill_keys($modules, FALSE);
-    // Let modules adjust the order solely based on the primary hook. This
-    // ensures the same module order regardless of whether this block
-    // runs. Calling $this->alter() recursively in this way does not
-    // result in an infinite loop, because this call is for a single
-    // $type, so we won't end up in this method again.
-    $this->alter('module_implements', $implementations, $hook);
-    return array_keys($implementations);
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -573,6 +545,7 @@ protected function getHookListeners(string $hook): array {
         }
       }
     }
+
     return $this->invokeMap[$hook] ?? [];
   }
 
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index f33fe4a4e601..00b7eddab3d0 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -113,7 +113,7 @@ public function process(ContainerBuilder $container): array {
     }
 
     // This can be removed when ModuleHandler::add() is removed.
-    if ($container->hasDefinition('module_handler')) {
+    if (count($container->getDefinitions()) > 1) {
       static::registerServices($container, $collector, $implementations, $moduleImplements ?? []);
       static::reOrderServices($container, $allOrderAttributes, $orderGroups, $implementations);
     }
-- 
GitLab


From a55aed83aa0106006108a8dbcc8a085f2356d518 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Wed, 11 Dec 2024 13:04:18 -0500
Subject: [PATCH 033/173] Unused module

---
 core/lib/Drupal/Core/Extension/ModuleHandler.php | 1 -
 1 file changed, 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 283f04c1b981..a1e17db7f863 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -438,7 +438,6 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
             }
             else {
               $hook_listeners[$module] = $listeners;
-              $extra_modules = TRUE;
             }
           }
         }
-- 
GitLab


From 3ffbc5e1b2db4e5cf69739f5eddcafb82463886b Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Wed, 11 Dec 2024 13:33:44 -0500
Subject: [PATCH 034/173] Need runtime alters

---
 .../Drupal/Core/Extension/ModuleHandler.php   | 31 ++++++++++++++++++-
 1 file changed, 30 insertions(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index a1e17db7f863..e5bc1ecfd5ca 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -438,6 +438,7 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
             }
             else {
               $hook_listeners[$module] = $listeners;
+              $extra_modules = TRUE;
             }
           }
         }
@@ -446,7 +447,9 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
       // 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;
@@ -458,6 +461,32 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
     }
   }
 
+  /**
+   * Reorder modules for alters.
+   *
+   * @param array $modules
+   *   A list of modules.
+   * @param string $hook
+   *   The hook being worked on, for example form_alter.
+   *
+   * @return array
+   *   The list, potentially reordered and changed by
+   *   hook_module_implements_alter().
+   */
+  protected function reOrderModulesForAlter(array $modules, string $hook): array {
+    // Order by module order first.
+    $modules = array_intersect(array_keys($this->moduleList), $modules);
+    // Alter expects the module list to be in the keys.
+    $implementations = array_fill_keys($modules, FALSE);
+    // Let modules adjust the order solely based on the primary hook. This
+    // ensures the same module order regardless of whether this block
+    // runs. Calling $this->alter() recursively in this way does not
+    // result in an infinite loop, because this call is for a single
+    // $type, so we won't end up in this method again.
+    $this->alter('module_implements', $implementations, $hook);
+    return array_keys($implementations);
+  }
+
   /**
    * {@inheritdoc}
    */
-- 
GitLab


From da2e9a581f5385affe100b23771efcac258df43e Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Thu, 12 Dec 2024 18:08:59 -0500
Subject: [PATCH 035/173] BC layer for hmia

---
 .../Drupal/Core/Extension/ModuleHandler.php   | 13 ++++-
 .../Drupal/Core/Hook/HookCollectorPass.php    | 52 ++++++++++++++-----
 2 files changed, 50 insertions(+), 15 deletions(-)

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index e5bc1ecfd5ca..d92764625fc9 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -80,11 +80,13 @@ class ModuleHandler implements ModuleHandlerInterface {
    *   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 $hooksOrderedByAttributes
+   *   An array of hooks that have been ordered by attributes.
    *
    * @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 $hooksOrderedByAttributes = []) {
     $this->root = $root;
     $this->moduleList = [];
     foreach ($module_list as $name => $module) {
@@ -448,7 +450,14 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
       // appropriate order.
       $modules = array_keys($hook_listeners);
       if (isset($extra_modules)) {
-        $modules = $this->reOrderModulesForAlter($modules, $hook);
+        $orderingDone = FALSE;
+        if (count(array_intersect($extra_types, $this->hooksOrderedByAttributes)) === count($extra_types)) {
+          $orderingDone = TRUE;
+        }
+
+        if (!$orderingDone) {
+          $modules = $this->reOrderModulesForAlter($modules, $hook);
+        }
       }
       foreach ($modules as $module) {
         foreach ($hook_listeners[$module] ?? [] as $listener) {
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 00b7eddab3d0..4861b7d539e4 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -114,12 +114,30 @@ public function process(ContainerBuilder $container): array {
 
     // This can be removed when ModuleHandler::add() is removed.
     if (count($container->getDefinitions()) > 1) {
-      static::registerServices($container, $collector, $implementations, $moduleImplements ?? []);
+      static::registerServices($container, $collector, $implementations, $moduleImplements ?? [], $orderGroups);
       static::reOrderServices($container, $allOrderAttributes, $orderGroups, $implementations);
     }
     return $implementations;
   }
 
+  /**
+   * @param mixed $hook
+   *   The hook to get from orderGroups.
+   * @param array $orderGroups
+   *   The hooks that have HookOrderGroup attributes.
+   *
+   * @return array
+   */
+  protected static function getHooks(string $hook, array $orderGroups): array {
+    $hooks = [$hook];
+    foreach ($orderGroups as $group) {
+      if (in_array($hook, $group)) {
+        $hooks = array_merge($hooks, $group);
+      }
+    }
+    return array_unique($hooks);
+  }
+
   /**
    * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
    *   The container.
@@ -127,12 +145,14 @@ public function process(ContainerBuilder $container): array {
    *   The collector.
    * @param array $implementations
    *   All implementations.
-   * @param array $allModuleImplements
+   * @param array $legacyImplementations
    *   Modules that implement hooks.
+   * @param array $reorderGroups
+   *   Groups of hooks to reorder.
    *
    * @return void
    */
-  protected static function registerServices(ContainerBuilder $container, HookCollectorPass $collector, array $implementations, array $allModuleImplements): void {
+  protected static function registerServices(ContainerBuilder $container, HookCollectorPass $collector, array $implementations, array $legacyImplementations, array $reorderGroups): void {
     $container->register(ProceduralCall::class, ProceduralCall::class)
       ->addArgument($collector->includes);
     $groupIncludes = [];
@@ -143,9 +163,17 @@ protected static function registerServices(ContainerBuilder $container, HookColl
         }
       }
     }
-    $definition = $container->getDefinition('module_handler');
-    $definition->setArgument('$groupIncludes', $groupIncludes);
-    foreach ($allModuleImplements as $hook => $moduleImplements) {
+
+    $hooksOrderedByAttribute = [];
+    foreach ($legacyImplementations as $hook => $moduleImplements) {
+      $getHooks = self::getHooks($hook, $reorderGroups);
+      $count = count($getHooks);
+      foreach ($getHooks as $extraHook) {
+        $moduleImplements += $legacyImplementations[$extraHook] ?? [];
+        if ($count > 1) {
+          $hooksOrderedByAttribute[] = str_replace('_alter', '', $extraHook);
+        }
+      }
       foreach ($collector->moduleImplementsAlters as $alter) {
         $alter($moduleImplements, $hook);
       }
@@ -169,8 +197,12 @@ protected static function registerServices(ContainerBuilder $container, HookColl
             ]);
           }
         }
+        unset($implementations[$hook][$module]);
       }
     }
+    $definition = $container->getDefinition('module_handler');
+    $definition->setArgument('$groupIncludes', $groupIncludes);
+    $definition->setArgument('$hooksOrderedByAttributes', array_unique($hooksOrderedByAttribute) ?? []);
     $container->setParameter('hook_implementations_map', $map ?? []);
   }
 
@@ -189,13 +221,7 @@ protected static function registerServices(ContainerBuilder $container, HookColl
   protected static function reOrderServices(ContainerBuilder $container, array $allOrderAttributes, array $orderGroups, array $implementations): void {
     $hookPriority = new HookPriority($container);
     foreach ($allOrderAttributes as $orderAttribute) {
-      $hooks = [$orderAttribute->hook];
-      foreach ($orderGroups as $group) {
-        if (in_array($orderAttribute->hook, $group)) {
-          $hooks = array_merge($hooks, $group);
-        }
-      }
-      $hooks = array_unique($hooks);
+      $hooks = self::getHooks($orderAttribute->hook, $orderGroups);
       if (isset($orderAttribute->modules)) {
         $others = [];
         foreach ($orderAttribute->modules as $module) {
-- 
GitLab


From 27090748c30f86778bd1d46e84fda0e6b2e99b41 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Thu, 12 Dec 2024 18:34:51 -0500
Subject: [PATCH 036/173] Stan

---
 core/lib/Drupal/Core/Extension/ModuleHandler.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index d92764625fc9..faed65ce8af3 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -451,7 +451,7 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
       $modules = array_keys($hook_listeners);
       if (isset($extra_modules)) {
         $orderingDone = FALSE;
-        if (count(array_intersect($extra_types, $this->hooksOrderedByAttributes)) === count($extra_types)) {
+        if (isset($extra_types) && count(array_intersect($extra_types, $this->hooksOrderedByAttributes)) === count($extra_types)) {
           $orderingDone = TRUE;
         }
 
-- 
GitLab


From 427edbf4de708f25245965c90b21b1645626066d Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Thu, 12 Dec 2024 18:49:25 -0500
Subject: [PATCH 037/173] Sometimes a module does not implement a hook

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 4861b7d539e4..f4959c12a1fd 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -179,7 +179,7 @@ protected static function registerServices(ContainerBuilder $container, HookColl
       }
       $priority = 0;
       foreach ($moduleImplements as $module => $v) {
-        foreach ($implementations[$hook][$module] as $class => $method_hooks) {
+        foreach ($implementations[$hook][$module] ?? [] as $class => $method_hooks) {
           if ($container->has($class)) {
             $definition = $container->findDefinition($class);
           }
-- 
GitLab


From 35f4d5bfc542e7ac2315d25d8e434573e2914ec8 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Thu, 12 Dec 2024 19:21:50 -0500
Subject: [PATCH 038/173] Handle update hooks

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index f4959c12a1fd..317a71bb9b1a 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -166,7 +166,7 @@ protected static function registerServices(ContainerBuilder $container, HookColl
 
     $hooksOrderedByAttribute = [];
     foreach ($legacyImplementations as $hook => $moduleImplements) {
-      $getHooks = self::getHooks($hook, $reorderGroups);
+      $getHooks = self::getHooks((string) $hook, $reorderGroups);
       $count = count($getHooks);
       foreach ($getHooks as $extraHook) {
         $moduleImplements += $legacyImplementations[$extraHook] ?? [];
-- 
GitLab


From e58755a80891f5ca20a8283fa477736f72bd470f Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Thu, 12 Dec 2024 19:23:32 -0500
Subject: [PATCH 039/173] Other call

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 317a71bb9b1a..c43e926e2e39 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -221,7 +221,7 @@ protected static function registerServices(ContainerBuilder $container, HookColl
   protected static function reOrderServices(ContainerBuilder $container, array $allOrderAttributes, array $orderGroups, array $implementations): void {
     $hookPriority = new HookPriority($container);
     foreach ($allOrderAttributes as $orderAttribute) {
-      $hooks = self::getHooks($orderAttribute->hook, $orderGroups);
+      $hooks = self::getHooks((string) $orderAttribute->hook, $orderGroups);
       if (isset($orderAttribute->modules)) {
         $others = [];
         foreach ($orderAttribute->modules as $module) {
-- 
GitLab


From 7f74f13447ca95bc695eaeb22969885b6878f45f Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Thu, 12 Dec 2024 20:13:25 -0500
Subject: [PATCH 040/173] Simplify already ordered

---
 core/lib/Drupal/Core/Extension/ModuleHandler.php | 11 ++---------
 1 file changed, 2 insertions(+), 9 deletions(-)

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index faed65ce8af3..4a64fe6defa4 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -449,15 +449,8 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
       // 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)) {
-        $orderingDone = FALSE;
-        if (isset($extra_types) && count(array_intersect($extra_types, $this->hooksOrderedByAttributes)) === count($extra_types)) {
-          $orderingDone = TRUE;
-        }
-
-        if (!$orderingDone) {
-          $modules = $this->reOrderModulesForAlter($modules, $hook);
-        }
+      if (isset($extra_modules) && array_diff($extra_types, $this->hooksOrderedByAttributes)) {
+        $modules = $this->reOrderModulesForAlter($modules, $hook);
       }
       foreach ($modules as $module) {
         foreach ($hook_listeners[$module] ?? [] as $listener) {
-- 
GitLab


From 533b851a29d6273b801a338886233d64a1df4c46 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Thu, 12 Dec 2024 20:13:41 -0500
Subject: [PATCH 041/173] Docs and simplification of interfaces

---
 .../Drupal/Core/Hook/Attribute/HookAfter.php  |  4 +--
 .../Drupal/Core/Hook/Attribute/HookBefore.php |  4 +--
 .../Drupal/Core/Hook/Attribute/HookFirst.php  |  2 ++
 .../Drupal/Core/Hook/Attribute/HookLast.php   |  2 ++
 .../Core/Hook/Attribute/HookOrderBase.php     | 29 +++++++++++--------
 .../Hook/Attribute/HookOrderInterface.php     | 19 ++++++++----
 .../Drupal/Core/Hook/HookCollectorPass.php    |  5 +---
 7 files changed, 40 insertions(+), 25 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
index c1ee29d48ad9..ead9cb498dc2 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
@@ -5,10 +5,10 @@
 namespace Drupal\Core\Hook\Attribute;
 
 /**
- * Attribute for marking that a hook should be changed.
+ * Attribute for marking that a hook's order should be changed.
  *
  * This allows you to ensure the hook is executed after
- * a specific hook in another module.
+ * hooks in other modules.
  *
  * @section sec_backwards_compatibility Backwards-compatibility
  *
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
index cdbef7f0e983..33658b1fbe10 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
@@ -5,10 +5,10 @@
 namespace Drupal\Core\Hook\Attribute;
 
 /**
- * Attribute for marking that a hook should be changed.
+ * Attribute for marking that a hook's order should be changed.
  *
  * This allows you to ensure the hook is executed before
- * a specific hook in another module.
+ * hooks in other modules.
  *
  * @section sec_backwards_compatibility Backwards-compatibility
  *
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php b/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
index 774d74f0bb29..45f98cd6d87b 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
@@ -7,6 +7,8 @@
 /**
  * Attribute for marking that a hook should be executed first.
  *
+ * This makes sure that this hook runs before all other hooks of the same type.
+ *
  * @section sec_backwards_compatibility Backwards-compatibility
  *
  * To allow hook implementations to work on older versions of Drupal as well,
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookLast.php b/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
index 2f23ef11c055..759b6df2e301 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
@@ -7,6 +7,8 @@
 /**
  * Attribute for marking that a hook should be executed last.
  *
+ * This makes sure that this hook runs after all other hooks of the same type.
+ *
  * @section sec_backwards_compatibility Backwards-compatibility
  *
  * To allow hook implementations to work on older versions of Drupal as well,
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
index 752fcea02c71..e207f193c3d2 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
@@ -4,38 +4,43 @@
 
 namespace Drupal\Core\Hook\Attribute;
 
+/**
+ * Common set of functionality needed by attributes that handle ordering hooks.
+ */
 class HookOrderBase implements HookOrderInterface {
 
   /**
+   * The hook that should be ordered.
+   *
    * @internal
    */
   public string $hook;
 
   /**
+   * The class the hook is found in.
+   *
    * @internal
    */
   public string $class;
 
   /**
+   * The method of the hook.
+   *
    * @internal
    */
   public string $method;
 
-  public function __construct(public readonly bool $shouldBeLarger) {
-
-  }
+  /**
+   * Constructs a HookOrderBase class.
+   */
+  public function __construct(public readonly bool $shouldBeLarger) {}
 
-  public function setHook(string $hook): static {
+  /**
+   * {@inheritdoc}
+   */
+  public function set(string $hook, string $class, string $method): static {
     $this->hook = $hook;
-    return $this;
-  }
-
-  public function setClass(string $class): static {
     $this->class = $class;
-    return $this;
-  }
-
-  public function setMethod(string $method): static {
     $this->method = $method;
     return $this;
   }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
index c5406a63801e..be9d8b18825d 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
@@ -4,12 +4,21 @@
 
 namespace Drupal\Core\Hook\Attribute;
 
+/**
+ * Interface for classes that manage hook ordering.
+ */
 interface HookOrderInterface {
 
-  public function setHook(string $hook): static;
-
-  public function setClass(string $class): static;
-
-  public function setMethod(string $method): static;
+  /**
+   * Set the properties on the attributes using this class.
+   *
+   * @param string $hook
+   *   The hook to order.
+   * @param string $class
+   *   The class the hook is in.
+   * @param string $method
+   *   The method of the hook.
+   */
+  public function set(string $hook, string $class, string $method): static;
 
 }
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index c43e926e2e39..da00a7577d4e 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -99,10 +99,7 @@ public function process(ContainerBuilder $container): array {
           }
           if ($hook) {
             foreach ($orderAttributes as $orderAttribute) {
-              $allOrderAttributes[] = $orderAttribute
-                ->setHook($hook)
-                ->setClass($class)
-                ->setMethod($method);
+              $allOrderAttributes[] = $orderAttribute->set(hook: $hook, class: $class, method: $method);
             }
             if ($orderGroup) {
               $orderGroups[] = array_merge($orderGroup, [$hook]);
-- 
GitLab


From 38c01b8d6f9cfae232b0bf672d3d65d458435fee Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Thu, 12 Dec 2024 20:25:10 -0500
Subject: [PATCH 042/173] Clean up extra hook storage

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 32 ++++++-------------
 1 file changed, 9 insertions(+), 23 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index da00a7577d4e..b01373097556 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -102,12 +102,16 @@ public function process(ContainerBuilder $container): array {
               $allOrderAttributes[] = $orderAttribute->set(hook: $hook, class: $class, method: $method);
             }
             if ($orderGroup) {
-              $orderGroups[] = array_merge($orderGroup, [$hook]);
+              $orderGroup[] = $hook;
+              foreach ($orderGroup as $extraHook) {
+                $orderGroups[$extraHook] = array_merge($orderGroups[$extraHook] ?? [], $orderGroup);
+              }
             }
           }
         }
       }
     }
+    $orderGroups = array_map('array_unique', $orderGroups);
 
     // This can be removed when ModuleHandler::add() is removed.
     if (count($container->getDefinitions()) > 1) {
@@ -117,24 +121,6 @@ public function process(ContainerBuilder $container): array {
     return $implementations;
   }
 
-  /**
-   * @param mixed $hook
-   *   The hook to get from orderGroups.
-   * @param array $orderGroups
-   *   The hooks that have HookOrderGroup attributes.
-   *
-   * @return array
-   */
-  protected static function getHooks(string $hook, array $orderGroups): array {
-    $hooks = [$hook];
-    foreach ($orderGroups as $group) {
-      if (in_array($hook, $group)) {
-        $hooks = array_merge($hooks, $group);
-      }
-    }
-    return array_unique($hooks);
-  }
-
   /**
    * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
    *   The container.
@@ -163,9 +149,9 @@ protected static function registerServices(ContainerBuilder $container, HookColl
 
     $hooksOrderedByAttribute = [];
     foreach ($legacyImplementations as $hook => $moduleImplements) {
-      $getHooks = self::getHooks((string) $hook, $reorderGroups);
-      $count = count($getHooks);
-      foreach ($getHooks as $extraHook) {
+      $extraHooks = $reorderGroups[$hook] ?? [];
+      $count = count($extraHooks);
+      foreach ($extraHooks as $extraHook) {
         $moduleImplements += $legacyImplementations[$extraHook] ?? [];
         if ($count > 1) {
           $hooksOrderedByAttribute[] = str_replace('_alter', '', $extraHook);
@@ -218,7 +204,7 @@ protected static function registerServices(ContainerBuilder $container, HookColl
   protected static function reOrderServices(ContainerBuilder $container, array $allOrderAttributes, array $orderGroups, array $implementations): void {
     $hookPriority = new HookPriority($container);
     foreach ($allOrderAttributes as $orderAttribute) {
-      $hooks = self::getHooks((string) $orderAttribute->hook, $orderGroups);
+      $hooks = $orderGroups[$orderAttribute->hook] ?? [];
       if (isset($orderAttribute->modules)) {
         $others = [];
         foreach ($orderAttribute->modules as $module) {
-- 
GitLab


From ec70880f53710185c487fb818aafaabbc951c49f Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Thu, 12 Dec 2024 21:27:18 -0500
Subject: [PATCH 043/173] Add tests for hookOrderGroups

---
 .../Drupal/Core/Hook/HookCollectorPass.php    |  4 +-
 .../src/Hook/FirstAlphabeticallyHooks1.php    | 15 +---
 .../src/Hook/FirstAlphabeticallyHooks2.php    |  6 +-
 .../hook_order_first_alphabetically4.info.yml |  6 ++
 .../src/Hook/FirstAlphabeticallyHooks4.php    | 29 ++++++++
 .../hook_order_first_alphabetically5.info.yml |  6 ++
 .../src/Hook/FirstAlphabeticallyHooks5.php    | 24 ++++++
 .../src/Hook/LastAlphabeticallyHooks1.php     | 17 +----
 .../src/Hook/LastAlphabeticallyHooks2.php     |  4 +-
 .../hook_order_last_alphabetically4.info.yml  |  6 ++
 .../src/Hook/LastAlphabeticallyHooks4.php     | 22 ++++++
 .../hook_order_last_alphabetically5.info.yml  |  6 ++
 .../src/Hook/LastAlphabeticallyHooks5.php     | 25 +++++++
 .../Core/Hook/HookCollectorPassTest.php       | 74 +++++++++++++++----
 14 files changed, 196 insertions(+), 48 deletions(-)
 create mode 100644 core/modules/system/tests/modules/hook_order_first_alphabetically4/hook_order_first_alphabetically4.info.yml
 create mode 100644 core/modules/system/tests/modules/hook_order_first_alphabetically4/src/Hook/FirstAlphabeticallyHooks4.php
 create mode 100644 core/modules/system/tests/modules/hook_order_first_alphabetically5/hook_order_first_alphabetically5.info.yml
 create mode 100644 core/modules/system/tests/modules/hook_order_first_alphabetically5/src/Hook/FirstAlphabeticallyHooks5.php
 create mode 100644 core/modules/system/tests/modules/hook_order_last_alphabetically4/hook_order_last_alphabetically4.info.yml
 create mode 100644 core/modules/system/tests/modules/hook_order_last_alphabetically4/src/Hook/LastAlphabeticallyHooks4.php
 create mode 100644 core/modules/system/tests/modules/hook_order_last_alphabetically5/hook_order_last_alphabetically5.info.yml
 create mode 100644 core/modules/system/tests/modules/hook_order_last_alphabetically5/src/Hook/LastAlphabeticallyHooks5.php

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index b01373097556..3ea550ab7639 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -204,7 +204,9 @@ protected static function registerServices(ContainerBuilder $container, HookColl
   protected static function reOrderServices(ContainerBuilder $container, array $allOrderAttributes, array $orderGroups, array $implementations): void {
     $hookPriority = new HookPriority($container);
     foreach ($allOrderAttributes as $orderAttribute) {
-      $hooks = $orderGroups[$orderAttribute->hook] ?? [];
+      // ::process() adds the hook serving as key to the order group so it
+      // does not need to be added if there's a group for the hook.
+      $hooks = $orderGroups[$orderAttribute->hook] ?? [$orderAttribute->hook];
       if (isset($orderAttribute->modules)) {
         $others = [];
         foreach ($orderAttribute->modules as $module) {
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php b/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php
index d9e190560719..daf9769ff73f 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php
@@ -5,7 +5,6 @@
 namespace Drupal\hook_order_first_alphabetically1\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\HookLast;
 
 /**
  * Hook implementations for hook_order_first_alphabetically.
@@ -13,23 +12,13 @@
 class FirstAlphabeticallyHooks1 {
 
   /**
-   * After LastAlphabeticallyHooks1::cacheFlush1.
+   * After LastAlphabeticallyHooks1::cacheFlush.
    */
   #[Hook('cache_flush')]
-  public static function cacheFlush1(): void {
+  public static function cacheFlush(): void {
     if (!isset($GLOBALS['HookFirst'])) {
       $GLOBALS['HookOutOfOrderTestingFirst'] = 'HookOutOfOrderTestingFirst';
     }
     $GLOBALS['HookRanTestingFirst'] = 'HookRanTestingFirst';
   }
-
-  /**
-   * After LastAlphabeticallyHooks1::cacheFlush2.
-   */
-  #[HookLast]
-  #[Hook('cache_flush')]
-  public static function cacheFlush2(): void {
-    $GLOBALS['HookLast'] = 'HookLast';
-  }
-
 }
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php b/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php
index 920a685d5f67..c0c2f254d337 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php
@@ -6,7 +6,6 @@
 
 use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\Core\Hook\Attribute\HookAfter;
-use Drupal\Core\Hook\Attribute\HookOrderGroup;
 
 /**
  * Hook implementations for hook_order_first_alphabetically.
@@ -14,12 +13,11 @@
 class FirstAlphabeticallyHooks2 {
 
   /**
-   * After LastAlphabeticallyHooks2::cacheFlush1.
+   * After LastAlphabeticallyHooks2::cacheFlush.
    */
   #[HookAfter(['hook_order_last_alphabetically2'])]
-  #[HookOrderGroup(['cache_flush'])]
   #[Hook('cache_flush')]
-  public static function cacheFlush1(): void {
+  public static function cacheFlush(): void {
     $GLOBALS['HookAfter'] = 'HookAfter';
   }
 
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically4/hook_order_first_alphabetically4.info.yml b/core/modules/system/tests/modules/hook_order_first_alphabetically4/hook_order_first_alphabetically4.info.yml
new file mode 100644
index 000000000000..d61a2ddc1212
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically4/hook_order_first_alphabetically4.info.yml
@@ -0,0 +1,6 @@
+name: first alphabetically
+type: module
+description: 'Test module used to test hook ordering.'
+package: Testing
+version: VERSION
+hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically4/src/Hook/FirstAlphabeticallyHooks4.php b/core/modules/system/tests/modules/hook_order_first_alphabetically4/src/Hook/FirstAlphabeticallyHooks4.php
new file mode 100644
index 000000000000..84c2c1e001c1
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically4/src/Hook/FirstAlphabeticallyHooks4.php
@@ -0,0 +1,29 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_first_alphabetically4\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Attribute\HookAfter;
+use Drupal\Core\Hook\Attribute\HookOrderGroup;
+
+/**
+ * Hook implementations for hook_order_first_alphabetically.
+ */
+class FirstAlphabeticallyHooks4 {
+
+  /**
+   * After LastAlphabeticallyHooks4::customHookExtraTypes.
+   */
+  #[HookAfter(['hook_order_last_alphabetically4'])]
+  #[HookOrderGroup(['custom_hook_extra_types2_alter'])]
+  #[Hook('custom_hook_extra_types1_alter')]
+  public static function customHookExtraTypes(): void {
+    if (!isset($GLOBALS['HookOrderGroupExtraTypes'])) {
+      $GLOBALS['HookOutOfOrderTestingOrderGroupsExtraTypes'] = 'HookOutOfOrderTestingOrderGroupsExtraTypes';
+    }
+    $GLOBALS['HookRanTestingOrderGroupsExtraTypes'] = 'HookRanTestingOrderGroupsExtraTypes';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically5/hook_order_first_alphabetically5.info.yml b/core/modules/system/tests/modules/hook_order_first_alphabetically5/hook_order_first_alphabetically5.info.yml
new file mode 100644
index 000000000000..d61a2ddc1212
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically5/hook_order_first_alphabetically5.info.yml
@@ -0,0 +1,6 @@
+name: first alphabetically
+type: module
+description: 'Test module used to test hook ordering.'
+package: Testing
+version: VERSION
+hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically5/src/Hook/FirstAlphabeticallyHooks5.php b/core/modules/system/tests/modules/hook_order_first_alphabetically5/src/Hook/FirstAlphabeticallyHooks5.php
new file mode 100644
index 000000000000..8b687c359738
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically5/src/Hook/FirstAlphabeticallyHooks5.php
@@ -0,0 +1,24 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_first_alphabetically5\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Attribute\HookLast;
+
+/**
+ * Hook implementations for hook_order_first_alphabetically.
+ */
+class FirstAlphabeticallyHooks5 {
+
+  /**
+   * After LastAlphabeticallyHooks5::cacheFlush.
+   */
+  #[HookLast]
+  #[Hook('cache_flush')]
+  public static function cacheFlush(): void {
+    $GLOBALS['HookLast'] = 'HookLast';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php b/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php
index a90d8e6b67fb..83ee0b3b09d1 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php
@@ -6,6 +6,7 @@
 
 use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\Core\Hook\Attribute\HookFirst;
+use Drupal\Core\Hook\Attribute\HookOrderGroup;
 
 /**
  * Hook implementations for hook_order_last_alphabetically.
@@ -13,23 +14,13 @@
 class LastAlphabeticallyHooks1 {
 
   /**
-   * Before FirstAlphabeticallyHooks1::cacheFlush1.
+   * Before FirstAlphabeticallyHooks1::cacheFlush.
    */
   #[HookFirst]
   #[Hook('cache_flush')]
-  public static function cacheFlush1(): void {
+  #[HookOrderGroup(['cache_flush'])]
+  public static function cacheFlush(): void {
     $GLOBALS['HookFirst'] = 'HookFirst';
   }
 
-  /**
-   * Before FirstAlphabeticallyHooks1::cacheFlush2.
-   */
-  #[Hook('cache_flush')]
-  public static function cacheFlush2(): void {
-    if (isset($GLOBALS['HookLast'])) {
-      $GLOBALS['HookOutOfOrderTestingLast'] = 'HookOutOfOrderTestingLast';
-    }
-    $GLOBALS['HookRanTestingLast'] = 'HookRanTestingLast';
-  }
-
 }
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php b/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php
index b95ceb3a8531..4210d9364608 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php
@@ -12,10 +12,10 @@
 class LastAlphabeticallyHooks2 {
 
   /**
-   * Before FirstAlphabeticallyHooks2::cacheFlush1.
+   * Before FirstAlphabeticallyHooks2::cacheFlush.
    */
   #[Hook('cache_flush')]
-  public static function cacheFlush1(): void {
+  public static function cacheFlush(): void {
     // This should be run before so HookAfter should not be set.
     if (isset($GLOBALS['HookAfter'])) {
       $GLOBALS['HookOutOfOrderTestingAfter'] = 'HookOutOfOrderTestingAfter';
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically4/hook_order_last_alphabetically4.info.yml b/core/modules/system/tests/modules/hook_order_last_alphabetically4/hook_order_last_alphabetically4.info.yml
new file mode 100644
index 000000000000..59334b38d2fc
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically4/hook_order_last_alphabetically4.info.yml
@@ -0,0 +1,6 @@
+name: Hook ordering last
+type: module
+description: 'Test module used to test hook ordering.'
+package: Testing
+version: VERSION
+hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically4/src/Hook/LastAlphabeticallyHooks4.php b/core/modules/system/tests/modules/hook_order_last_alphabetically4/src/Hook/LastAlphabeticallyHooks4.php
new file mode 100644
index 000000000000..37b36995e5f6
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically4/src/Hook/LastAlphabeticallyHooks4.php
@@ -0,0 +1,22 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_last_alphabetically4\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for hook_order_last_alphabetically.
+ */
+class LastAlphabeticallyHooks4 {
+
+  /**
+   * Before FirstAlphabeticallyHooks4::customHookExtraTypes.
+   */
+  #[Hook('custom_hook_extra_types2_alter')]
+  public static function customHookExtraTypes(): void {
+    $GLOBALS['HookOrderGroupExtraTypes'] = 'HookOrderGroupExtraTypes';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically5/hook_order_last_alphabetically5.info.yml b/core/modules/system/tests/modules/hook_order_last_alphabetically5/hook_order_last_alphabetically5.info.yml
new file mode 100644
index 000000000000..59334b38d2fc
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically5/hook_order_last_alphabetically5.info.yml
@@ -0,0 +1,6 @@
+name: Hook ordering last
+type: module
+description: 'Test module used to test hook ordering.'
+package: Testing
+version: VERSION
+hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically5/src/Hook/LastAlphabeticallyHooks5.php b/core/modules/system/tests/modules/hook_order_last_alphabetically5/src/Hook/LastAlphabeticallyHooks5.php
new file mode 100644
index 000000000000..cc6b117e4a9d
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically5/src/Hook/LastAlphabeticallyHooks5.php
@@ -0,0 +1,25 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_last_alphabetically5\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for hook_order_last_alphabetically.
+ */
+class LastAlphabeticallyHooks5 {
+
+  /**
+   * Before FirstAlphabeticallyHooks5::cacheFlush.
+   */
+  #[Hook('cache_flush')]
+  public static function cacheFlush(): void {
+    if (isset($GLOBALS['HookLast'])) {
+      $GLOBALS['HookOutOfOrderTestingLast'] = 'HookOutOfOrderTestingLast';
+    }
+    $GLOBALS['HookRanTestingLast'] = 'HookRanTestingLast';
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
index 28b38812974c..9412573232ca 100644
--- a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
@@ -124,27 +124,39 @@ public function testProceduralHooksSkippedWhenConfigured(): void {
   }
 
   /**
-   * Tests HookFirst.
+   * Test Hook attribute with named arguments, and class with invoke method.
+   */
+  public function testHookAttribute(): void {
+    $module_installer = $this->container->get('module_installer');
+    $this->assertTrue($module_installer->install(['hook_collector_hook_attribute']));
+    $this->assertFalse(isset($GLOBALS['hook_named_arguments']));
+    $this->assertFalse(isset($GLOBALS['hook_invoke_method']));
+    drupal_flush_all_caches();
+    $this->assertTrue(isset($GLOBALS['hook_named_arguments']));
+    $this->assertTrue(isset($GLOBALS['hook_invoke_method']));
+  }
+
+  /**
+   * Tests hook ordering with attributes.
    */
   public function testHookFirst(): void {
     $module_installer = $this->container->get('module_installer');
-    $module_handler = $this->container->get('module_handler');
     $this->assertTrue($module_installer->install(['hook_order_first_alphabetically1']));
     $this->assertTrue($module_installer->install(['hook_order_last_alphabetically1']));
     $this->assertFalse(isset($GLOBALS['HookFirst']));
     $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingFirst']));
     $this->assertFalse(isset($GLOBALS['HookRanTestingFirst']));
-    $this->assertFalse(isset($GLOBALS['HookLast']));
-    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingLast']));
-    $this->assertFalse(isset($GLOBALS['HookRanTestingLast']));
     drupal_flush_all_caches();
     $this->assertTrue(isset($GLOBALS['HookFirst']));
     $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingFirst']));
     $this->assertTrue(isset($GLOBALS['HookRanTestingFirst']));
-    $this->assertTrue(isset($GLOBALS['HookLast']));
-    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingLast']));
-    $this->assertTrue(isset($GLOBALS['HookRanTestingLast']));
+  }
 
+  /**
+   * Tests hook ordering with attributes.
+   */
+  public function testHookAfter(): void {
+    $module_installer = $this->container->get('module_installer');
     $this->assertTrue($module_installer->install(['hook_order_first_alphabetically2']));
     $this->assertTrue($module_installer->install(['hook_order_last_alphabetically2']));
     $this->assertFalse(isset($GLOBALS['HookAfter']));
@@ -154,7 +166,13 @@ public function testHookFirst(): void {
     $this->assertTrue(isset($GLOBALS['HookAfter']));
     $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingAfter']));
     $this->assertTrue(isset($GLOBALS['HookRanTestingAfter']));
+  }
 
+  /**
+   * Tests hook ordering with attributes.
+   */
+  public function testHookBefore(): void {
+    $module_installer = $this->container->get('module_installer');
     $this->assertTrue($module_installer->install(['hook_order_first_alphabetically3']));
     $this->assertTrue($module_installer->install(['hook_order_last_alphabetically3']));
     $this->assertFalse(isset($GLOBALS['HookBefore']));
@@ -167,16 +185,42 @@ public function testHookFirst(): void {
   }
 
   /**
-   * Test Hook attribute with named arguments, and class with invoke method.
+   * Tests hook ordering with attributes.
    */
-  public function testHookAttribute(): void {
+  public function testHookOrderGroup(): void {
     $module_installer = $this->container->get('module_installer');
-    $this->assertTrue($module_installer->install(['hook_collector_hook_attribute']));
-    $this->assertFalse(isset($GLOBALS['hook_named_arguments']));
-    $this->assertFalse(isset($GLOBALS['hook_invoke_method']));
+    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically4']));
+    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically4']));
+    $this->assertFalse(isset($GLOBALS['HookOrderGroupExtraTypes']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingOrderGroupsExtraTypes']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingOrderGroupsExtraTypes']));
+    $module_handler = $this->container->get('module_handler');
+    $hooks = [
+      'custom_hook',
+      'custom_hook_extra_types1',
+      'custom_hook_extra_types2',
+    ];
+    $data = ['hi'];
+    $module_handler->alter($hooks, $data);
+    $this->assertTrue(isset($GLOBALS['HookOrderGroupExtraTypes']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingOrderGroupsExtraTypes']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingOrderGroupsExtraTypes']));
+  }
+
+  /**
+   * Tests hook ordering with attributes.
+   */
+  public function testHookLast(): void {
+    $module_installer = $this->container->get('module_installer');
+    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically5']));
+    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically5']));
+    $this->assertFalse(isset($GLOBALS['HookLast']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingLast']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingLast']));
     drupal_flush_all_caches();
-    $this->assertTrue(isset($GLOBALS['hook_named_arguments']));
-    $this->assertTrue(isset($GLOBALS['hook_invoke_method']));
+    $this->assertTrue(isset($GLOBALS['HookLast']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingLast']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingLast']));
   }
 
 }
-- 
GitLab


From 4e6461ce096a8aa6f46b6fa0dd3b4a77697c0351 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Thu, 12 Dec 2024 22:05:07 -0500
Subject: [PATCH 044/173] Stan and CS

---
 core/lib/Drupal/Core/Extension/ModuleHandler.php                | 2 ++
 .../src/Hook/FirstAlphabeticallyHooks1.php                      | 1 +
 2 files changed, 3 insertions(+)

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 4a64fe6defa4..1bda754da47c 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -449,6 +449,8 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
       // the primary hook, we need to add them to the $modules array in their
       // appropriate order.
       $modules = array_keys($hook_listeners);
+      // If $extra_modules is set then $extra_types must be set.
+      /** @phpstan-ignore variable.undefined */
       if (isset($extra_modules) && array_diff($extra_types, $this->hooksOrderedByAttributes)) {
         $modules = $this->reOrderModulesForAlter($modules, $hook);
       }
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php b/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php
index daf9769ff73f..e61ba8bc229c 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php
@@ -21,4 +21,5 @@ public static function cacheFlush(): void {
     }
     $GLOBALS['HookRanTestingFirst'] = 'HookRanTestingFirst';
   }
+
 }
-- 
GitLab


From b1b4a6b9219489b8ddc82bd1c0d410097a813844 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Fri, 13 Dec 2024 14:50:51 -0500
Subject: [PATCH 045/173] Manage already ordered hooks

---
 .../Drupal/Core/Extension/ModuleHandler.php   | 25 +++++++++++----
 .../Drupal/Core/Hook/HookCollectorPass.php    | 17 +++++-----
 core/lib/Drupal/Core/Hook/HookPriority.php    | 32 +++++++++++++++----
 3 files changed, 52 insertions(+), 22 deletions(-)

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 1bda754da47c..b809ed7fc5cc 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -431,10 +431,8 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
       $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) {
+        $find_listeners = function ($hook) use (&$hook_listeners, &$extra_modules) {
+          foreach ($this->getHookListeners($hook) as $module => $listeners) {
             if (isset($hook_listeners[$module])) {
               $hook_listeners[$module] = array_merge($hook_listeners[$module], $listeners);
             }
@@ -443,15 +441,28 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
               $extra_modules = TRUE;
             }
           }
+        };
+        // For multiple hooks, we need $modules to contain every module that
+        // implements at least one of them in the correct order. Hooks already
+        // ordered by attributes are also ordered by
+        // hook_module_implements_alter() they don't need to be ordered again.
+        foreach (array_merge($extra_types, [$type]) as $extra_type) {
+          if (isset($this->hooksOrderedByAttributes[$extra_type])) {
+            $group = $this->hooksOrderedByAttributes[$extra_type];
+            krsort($group);
+            $find_listeners(implode(':', $group));
+            $extra_types = array_diff($extra_types, $group);
+          }
+        }
+        foreach ($extra_types as $extra_type) {
+          $find_listeners($extra_type . '_alter');
         }
       }
       // 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 $extra_modules is set then $extra_types must be set.
-      /** @phpstan-ignore variable.undefined */
-      if (isset($extra_modules) && array_diff($extra_types, $this->hooksOrderedByAttributes)) {
+      if (!empty($extra_modules) && !empty($extra_types)) {
         $modules = $this->reOrderModulesForAlter($modules, $hook);
       }
       foreach ($modules as $module) {
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 3ea550ab7639..84625242b085 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -147,15 +147,10 @@ protected static function registerServices(ContainerBuilder $container, HookColl
       }
     }
 
-    $hooksOrderedByAttribute = [];
     foreach ($legacyImplementations as $hook => $moduleImplements) {
       $extraHooks = $reorderGroups[$hook] ?? [];
-      $count = count($extraHooks);
       foreach ($extraHooks as $extraHook) {
         $moduleImplements += $legacyImplementations[$extraHook] ?? [];
-        if ($count > 1) {
-          $hooksOrderedByAttribute[] = str_replace('_alter', '', $extraHook);
-        }
       }
       foreach ($collector->moduleImplementsAlters as $alter) {
         $alter($moduleImplements, $hook);
@@ -183,9 +178,15 @@ protected static function registerServices(ContainerBuilder $container, HookColl
         unset($implementations[$hook][$module]);
       }
     }
+
+    $hooksOrderedByAttributes = [];
+    foreach ($reorderGroups as $key => $values) {
+      // Remove _alter from the end.
+      $hooksOrderedByAttributes[substr($key, 0, -6)] = $values;
+    }
     $definition = $container->getDefinition('module_handler');
     $definition->setArgument('$groupIncludes', $groupIncludes);
-    $definition->setArgument('$hooksOrderedByAttributes', array_unique($hooksOrderedByAttribute) ?? []);
+    $definition->setArgument('$hooksOrderedByAttributes', array_unique($hooksOrderedByAttributes) ?? []);
     $container->setParameter('hook_implementations_map', $map ?? []);
   }
 
@@ -213,7 +214,7 @@ protected static function reOrderServices(ContainerBuilder $container, array $al
           foreach ($hooks as $hook) {
             foreach ($implementations[$hook][$module] ?? [] as $class => $methods) {
               foreach ($methods as $method) {
-                $others[] = "$class::$method";
+                $others[] = [$class, $method,];
               }
             }
           }
@@ -222,7 +223,7 @@ protected static function reOrderServices(ContainerBuilder $container, array $al
       else {
         $others = NULL;
       }
-      $hookPriority->change($hooks, "$orderAttribute->class::$orderAttribute->method", $orderAttribute->shouldBeLarger, $others);
+      $hookPriority->change($hooks, $orderAttribute->class, $orderAttribute->method, $orderAttribute->shouldBeLarger, $others);
     }
   }
 
diff --git a/core/lib/Drupal/Core/Hook/HookPriority.php b/core/lib/Drupal/Core/Hook/HookPriority.php
index d0b909b3b60a..64479559614a 100644
--- a/core/lib/Drupal/Core/Hook/HookPriority.php
+++ b/core/lib/Drupal/Core/Hook/HookPriority.php
@@ -15,20 +15,38 @@ public function __construct(protected ContainerBuilder $container) {}
    *
    * @param array $hooks
    *   The name of the hook.
-   * @param string $class_and_method
-   *   Class and method separated by :: containing the hook implementation which
-   *   should be changed.
+   * @param string $class
+   *   Class containing the hook implementation which should be changed.
+   * @param string $method
+   *   Method of the hook implementation which should be changed.
    * @param bool $should_be_larger
    *   TRUE for before/first, FALSE for after/last. Larger priority listeners
    *   fire first.
    * @param array|null $others
    *   Other hook implementations to compare to, if any. The array is a list of
-   *   strings containing a class and method separated by ::.
+   *   arrays containing a class and method.
    *
    * @return void
    */
-  public function change(array $hooks, string $class_and_method, bool $should_be_larger, ?array $others = NULL): void {
+  public function change(array $hooks, string $class, string $method, bool $should_be_larger, ?array $others = NULL): void {
+    $class_and_method = "$class::$method";
+    if ($others) {
+      $other_specifiers = array_map(fn ($pair) => $pair[0] . '::' . $pair[1], $others);
+    }
     $events = array_map(fn ($hook) => "drupal_hook.$hook", $hooks);
+    if (count($hooks) > 1) {
+      krsort($hooks);
+      $combinedHookTag = implode(':', $hooks);
+      $others[] = [$class, $method];
+      foreach ($others as $other) {
+        $definition = $this->container->getDefinition($other[0]);
+        $definition->addTag('kernel.event_listener', [
+          'event' => "drupal_hook.$combinedHookTag",
+          'method' => $other[1],
+          'priority' => 0,
+        ]);
+      }
+    }
     foreach ($this->container->findTaggedServiceIds('kernel.event_listener') as $id => $attributes) {
       foreach ($attributes as $key => $tag) {
         if (in_array($tag['event'], $events)) {
@@ -43,10 +61,10 @@ public function change(array $hooks, string $class_and_method, bool $should_be_l
           if ($class_and_method === $specifier) {
             $index_this = $index;
           }
-          // $others is specified for before and after, for these compare only
+          // $others is defined for before and after, for these compare only
           // the priority of those. For first and last the priority of every
           // other hook matters.
-          elseif (!isset($others) || in_array($specifier, $others)) {
+          elseif (!isset($other_specifiers) || in_array($specifier, $other_specifiers)) {
             $priorities_other[] = $priority;
           }
         }
-- 
GitLab


From b545f1df42b11fb4af1fd11b1f131780d88ab29a Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Fri, 13 Dec 2024 15:02:30 -0500
Subject: [PATCH 046/173] Coding standards

---
 core/lib/Drupal/Core/Extension/ModuleHandler.php | 2 ++
 core/lib/Drupal/Core/Hook/HookCollectorPass.php  | 2 +-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index b809ed7fc5cc..2e80ef805f95 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -438,6 +438,8 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
             }
             else {
               $hook_listeners[$module] = $listeners;
+              // It is used below.
+              // @phpcs:ignore DrupalPractice.CodeAnalysis.VariableAnalysis.UnusedVariable
               $extra_modules = TRUE;
             }
           }
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 84625242b085..21f7f6827882 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -214,7 +214,7 @@ protected static function reOrderServices(ContainerBuilder $container, array $al
           foreach ($hooks as $hook) {
             foreach ($implementations[$hook][$module] ?? [] as $class => $methods) {
               foreach ($methods as $method) {
-                $others[] = [$class, $method,];
+                $others[] = [$class, $method];
               }
             }
           }
-- 
GitLab


From 925614f0a3783c7290336a1a257425b3c204d5f0 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Fri, 13 Dec 2024 17:56:29 -0500
Subject: [PATCH 047/173] grouped alters

---
 .../Drupal/Core/Extension/ModuleHandler.php   | 13 +++--
 .../Drupal/Core/Hook/Attribute/HookAfter.php  |  2 +-
 .../Drupal/Core/Hook/Attribute/HookBefore.php |  2 +-
 .../Core/Hook/Attribute/HookOrderBase.php     | 10 +++-
 .../Hook/Attribute/HookOrderInterface.php     |  4 +-
 .../Drupal/Core/Hook/HookCollectorPass.php    | 16 ++---
 core/lib/Drupal/Core/Hook/HookPriority.php    | 58 ++++++++++---------
 7 files changed, 59 insertions(+), 46 deletions(-)

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 2e80ef805f95..9b93b99418e7 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -444,20 +444,21 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
             }
           }
         };
+        $extra_hooks = array_map(fn ($x) => $x . '_alter', $extra_types);
         // For multiple hooks, we need $modules to contain every module that
         // implements at least one of them in the correct order. Hooks already
         // ordered by attributes are also ordered by
         // hook_module_implements_alter() they don't need to be ordered again.
-        foreach (array_merge($extra_types, [$type]) as $extra_type) {
-          if (isset($this->hooksOrderedByAttributes[$extra_type])) {
-            $group = $this->hooksOrderedByAttributes[$extra_type];
+        foreach (array_merge($extra_hooks, [$type . '_alter']) as $extra_hook) {
+          if (isset($this->hooksOrderedByAttributes[$extra_hook])) {
+            $group = $this->hooksOrderedByAttributes[$extra_hook];
             krsort($group);
             $find_listeners(implode(':', $group));
-            $extra_types = array_diff($extra_types, $group);
+            $extra_types = array_diff($extra_hooks, $group);
           }
         }
-        foreach ($extra_types as $extra_type) {
-          $find_listeners($extra_type . '_alter');
+        foreach ($extra_hooks as $extra_hook) {
+          $find_listeners($extra_hook);
         }
       }
       // If any modules implement one of the extra hooks that do not implement
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
index ead9cb498dc2..80199bc78c09 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
@@ -30,7 +30,7 @@ class HookAfter extends HookOrderBase {
    *   The module this implementation should run before.
    */
   public function __construct(
-    public readonly array $modules,
+    public array $modules,
   ) {
     parent::__construct(FALSE);
   }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
index 33658b1fbe10..e93daa0b83cd 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
@@ -30,7 +30,7 @@ class HookBefore extends HookOrderBase {
    *   The module this implementation should run before.
    */
   public function __construct(
-    public readonly array $modules,
+    public array $modules,
   ) {
     parent::__construct(TRUE);
   }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
index e207f193c3d2..9d482e18c3d1 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
@@ -30,6 +30,13 @@ class HookOrderBase implements HookOrderInterface {
    */
   public string $method;
 
+  /**
+   * The module of the hook.
+   *
+   * @internal
+   */
+  public string $module;
+
   /**
    * Constructs a HookOrderBase class.
    */
@@ -38,10 +45,11 @@ public function __construct(public readonly bool $shouldBeLarger) {}
   /**
    * {@inheritdoc}
    */
-  public function set(string $hook, string $class, string $method): static {
+  public function set(string $hook, string $class, string $method, string $module): static {
     $this->hook = $hook;
     $this->class = $class;
     $this->method = $method;
+    $this->module = $module;
     return $this;
   }
 
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
index be9d8b18825d..2e70e18f153f 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
@@ -18,7 +18,9 @@ interface HookOrderInterface {
    *   The class the hook is in.
    * @param string $method
    *   The method of the hook.
+   * @param string $module
+   *   The module of the hook.
    */
-  public function set(string $hook, string $class, string $method): static;
+  public function set(string $hook, string $class, string $method, string $module): static;
 
 }
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 21f7f6827882..77fd18129f5e 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -87,7 +87,7 @@ public function process(ContainerBuilder $container): array {
               if ($attribute->method) {
                 $method = $attribute->method;
               }
-              $moduleImplements[$hook][$hookModule] = '';
+              $legacyImplementations[$hook][$hookModule] = '';
               $implementations[$hook][$hookModule][$class][] = $method;
             }
             if ($attribute instanceof HookOrderInterface) {
@@ -99,7 +99,7 @@ public function process(ContainerBuilder $container): array {
           }
           if ($hook) {
             foreach ($orderAttributes as $orderAttribute) {
-              $allOrderAttributes[] = $orderAttribute->set(hook: $hook, class: $class, method: $method);
+              $allOrderAttributes[] = $orderAttribute->set(hook: $hook, class: $class, method: $method, module: $hookModule);
             }
             if ($orderGroup) {
               $orderGroup[] = $hook;
@@ -115,7 +115,7 @@ public function process(ContainerBuilder $container): array {
 
     // This can be removed when ModuleHandler::add() is removed.
     if (count($container->getDefinitions()) > 1) {
-      static::registerServices($container, $collector, $implementations, $moduleImplements ?? [], $orderGroups);
+      static::registerServices($container, $collector, $implementations, $legacyImplementations ?? [], $orderGroups);
       static::reOrderServices($container, $allOrderAttributes, $orderGroups, $implementations);
     }
     return $implementations;
@@ -155,6 +155,7 @@ protected static function registerServices(ContainerBuilder $container, HookColl
       foreach ($collector->moduleImplementsAlters as $alter) {
         $alter($moduleImplements, $hook);
       }
+      $legacyImplementations[$hook] = $moduleImplements;
       $priority = 0;
       foreach ($moduleImplements as $module => $v) {
         foreach ($implementations[$hook][$module] ?? [] as $class => $method_hooks) {
@@ -181,12 +182,11 @@ protected static function registerServices(ContainerBuilder $container, HookColl
 
     $hooksOrderedByAttributes = [];
     foreach ($reorderGroups as $key => $values) {
-      // Remove _alter from the end.
-      $hooksOrderedByAttributes[substr($key, 0, -6)] = $values;
+      $hooksOrderedByAttributes[$key] = $values;
     }
     $definition = $container->getDefinition('module_handler');
     $definition->setArgument('$groupIncludes', $groupIncludes);
-    $definition->setArgument('$hooksOrderedByAttributes', array_unique($hooksOrderedByAttributes) ?? []);
+    $definition->setArgument('$hooksOrderedByAttributes', $hooksOrderedByAttributes);
     $container->setParameter('hook_implementations_map', $map ?? []);
   }
 
@@ -214,7 +214,7 @@ protected static function reOrderServices(ContainerBuilder $container, array $al
           foreach ($hooks as $hook) {
             foreach ($implementations[$hook][$module] ?? [] as $class => $methods) {
               foreach ($methods as $method) {
-                $others[] = [$class, $method];
+                $others[] = [$class, $method, $module];
               }
             }
           }
@@ -223,7 +223,7 @@ protected static function reOrderServices(ContainerBuilder $container, array $al
       else {
         $others = NULL;
       }
-      $hookPriority->change($hooks, $orderAttribute->class, $orderAttribute->method, $orderAttribute->shouldBeLarger, $others);
+      $hookPriority->change($hooks, $orderAttribute, $others);
     }
   }
 
diff --git a/core/lib/Drupal/Core/Hook/HookPriority.php b/core/lib/Drupal/Core/Hook/HookPriority.php
index 64479559614a..4187918da4a9 100644
--- a/core/lib/Drupal/Core/Hook/HookPriority.php
+++ b/core/lib/Drupal/Core/Hook/HookPriority.php
@@ -4,6 +4,7 @@
 
 namespace Drupal\Core\Hook;
 
+use Drupal\Core\Hook\Attribute\HookOrderBase;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 
 class HookPriority {
@@ -15,45 +16,46 @@ public function __construct(protected ContainerBuilder $container) {}
    *
    * @param array $hooks
    *   The name of the hook.
-   * @param string $class
-   *   Class containing the hook implementation which should be changed.
-   * @param string $method
-   *   Method of the hook implementation which should be changed.
-   * @param bool $should_be_larger
-   *   TRUE for before/first, FALSE for after/last. Larger priority listeners
-   *   fire first.
+   * @param \Drupal\Core\Hook\Attribute\HookOrderBase $attribute
+   *   The order attribute.
    * @param array|null $others
    *   Other hook implementations to compare to, if any. The array is a list of
-   *   arrays containing a class and method.
-   *
-   * @return void
+   *   arrays containing a class, a method and module.
    */
-  public function change(array $hooks, string $class, string $method, bool $should_be_larger, ?array $others = NULL): void {
-    $class_and_method = "$class::$method";
+  public function change(array $hooks, HookOrderBase $attribute, ?array $others = NULL): void {
+    $class_and_method = "$attribute->class::$attribute->method";
     if ($others) {
       $other_specifiers = array_map(fn ($pair) => $pair[0] . '::' . $pair[1], $others);
     }
-    $events = array_map(fn ($hook) => "drupal_hook.$hook", $hooks);
     if (count($hooks) > 1) {
+      $map = $this->container->getParameter('hook_implementations_map');
       krsort($hooks);
       $combinedHookTag = implode(':', $hooks);
-      $others[] = [$class, $method];
-      foreach ($others as $other) {
-        $definition = $this->container->getDefinition($other[0]);
+      $event = "drupal_hook.$combinedHookTag";
+      $data = $others;
+      $data[] = [$attribute->class, $attribute->method, $attribute->module];
+      $priority = 0;
+      foreach ($data as [$class, $method, $module]) {
+        $definition = $this->container->findDefinition($class);
         $definition->addTag('kernel.event_listener', [
           'event' => "drupal_hook.$combinedHookTag",
-          'method' => $other[1],
-          'priority' => 0,
+          'method' => $method,
+          'priority' => $priority--,
         ]);
+        $map[$combinedHookTag][$class][$method] = $module;
       }
+      $this->container->setParameter('hook_implementations_map', $map);
+    }
+    else {
+      $event = 'drupal_hook.' . reset($hooks);
     }
-    foreach ($this->container->findTaggedServiceIds('kernel.event_listener') as $id => $attributes) {
-      foreach ($attributes as $key => $tag) {
-        if (in_array($tag['event'], $events)) {
+    foreach ($this->container->findTaggedServiceIds('kernel.event_listener') as $id => $tags) {
+      foreach ($tags as $key => $tag) {
+        if ($tag['event'] === $event) {
           $index = "$id.$key";
           $priority = $tag['priority'];
           // Symfony documents event listener priorities to be integers,
-          // HookCollectorPass sets them to be integers, ::setPriority() only
+          // HookCollectorPass sets them to be integers, ::set() only
           // accepts integers.
           assert(is_int($priority));
           $priorities[$index] = $priority;
@@ -65,7 +67,7 @@ public function change(array $hooks, string $class, string $method, bool $should
           // the priority of those. For first and last the priority of every
           // other hook matters.
           elseif (!isset($other_specifiers) || in_array($specifier, $other_specifiers)) {
-            $priorities_other[] = $priority;
+            $priorities_other[$specifier] = $priority;
           }
         }
       }
@@ -76,15 +78,15 @@ public function change(array $hooks, string $class, string $method, bool $should
     // The priority of the hook being changed.
     $priority_this = $priorities[$index_this];
     // The priority of the hook being compared to.
-    $priority_other = $should_be_larger ? max($priorities_other) : min($priorities_other);
+    $priority_other = $attribute->shouldBeLarger ? max($priorities_other) : min($priorities_other);
     // If the order is correct there is nothing to do. If the two priorities
     // are the same then the order is undefined and so it can't be correct.
     // If they are not the same and $priority_this is already larger exactly
-    // when $should_be_larger says then it's the correct order.
-    if ($priority_this !== $priority_other && ($should_be_larger === ($priority_this > $priority_other))) {
+    // when $attribute->shouldBeLarger says then it's the correct order.
+    if ($priority_this !== $priority_other && ($attribute->shouldBeLarger === ($priority_this > $priority_other))) {
       return;
     }
-    $priority_new = $priority_other + ($should_be_larger ? 1 : -1);
+    $priority_new = $priority_other + ($attribute->shouldBeLarger ? 1 : -1);
     // For first and last this new priority is already larger/smaller
     // than all existing priorities but for before / after it might belong to
     // an already existing hook. In this case set the new priority temporarily
@@ -94,7 +96,7 @@ public function change(array $hooks, string $class, string $method, bool $should
     // relative to both $priority_other and the hook whose priority was
     // $priority_new.
     if (in_array($priority_new, $priorities)) {
-      $priorities[$index_this] = $priority_other + ($should_be_larger ? 0.5 : -0.5);
+      $priorities[$index_this] = $priority_other + ($attribute->shouldBeLarger ? 0.5 : -0.5);
       asort($priorities);
       $changed_indexes = array_keys($priorities);
       $priorities = array_combine($changed_indexes, range(1, count($changed_indexes)));
-- 
GitLab


From 626fe61aabe28e20804e480814819f16f2ea8c4a Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Fri, 13 Dec 2024 18:48:51 -0500
Subject: [PATCH 048/173] Stan

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 77fd18129f5e..c3ebb3ad0df9 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -99,6 +99,9 @@ public function process(ContainerBuilder $container): array {
           }
           if ($hook) {
             foreach ($orderAttributes as $orderAttribute) {
+              // $hookModule is set in the same clause as $hook
+              // if $hook is set then $hookModule is.
+              /** @phpstan-ignore variable.undefined */
               $allOrderAttributes[] = $orderAttribute->set(hook: $hook, class: $class, method: $method, module: $hookModule);
             }
             if ($orderGroup) {
-- 
GitLab


From fe5ab922887ef4d6bb28735af41d144ed8307434 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Fri, 13 Dec 2024 20:58:24 -0500
Subject: [PATCH 049/173] Fix all the alters

---
 .../Drupal/Core/Extension/ModuleHandler.php   | 49 +++++++++++--------
 1 file changed, 29 insertions(+), 20 deletions(-)

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 9b93b99418e7..207db740edca 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -430,21 +430,13 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
       $this->alterEventListeners[$cid] = [];
       $hook = $type . '_alter';
       $hook_listeners = $this->getHookListeners($hook);
+      $extra_modules = FALSE;
+      $extra_listeners = [];
       if (isset($extra_types)) {
-        $find_listeners = function ($hook) use (&$hook_listeners, &$extra_modules) {
-          foreach ($this->getHookListeners($hook) as $module => $listeners) {
-            if (isset($hook_listeners[$module])) {
-              $hook_listeners[$module] = array_merge($hook_listeners[$module], $listeners);
-            }
-            else {
-              $hook_listeners[$module] = $listeners;
-              // It is used below.
-              // @phpcs:ignore DrupalPractice.CodeAnalysis.VariableAnalysis.UnusedVariable
-              $extra_modules = TRUE;
-            }
-          }
-        };
         $extra_hooks = array_map(fn ($x) => $x . '_alter', $extra_types);
+        foreach ($extra_hooks as $extra_hook) {
+          $hook_listeners = $this->findListenersForAlter($extra_hook, $hook_listeners, $extra_modules);
+        }
         // For multiple hooks, we need $modules to contain every module that
         // implements at least one of them in the correct order. Hooks already
         // ordered by attributes are also ordered by
@@ -453,20 +445,22 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
           if (isset($this->hooksOrderedByAttributes[$extra_hook])) {
             $group = $this->hooksOrderedByAttributes[$extra_hook];
             krsort($group);
-            $find_listeners(implode(':', $group));
+            $extra_listeners = $this->findListenersForAlter(implode(':', $group));
             $extra_types = array_diff($extra_hooks, $group);
           }
         }
-        foreach ($extra_hooks as $extra_hook) {
-          $find_listeners($extra_hook);
-        }
       }
       // 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 (!empty($extra_modules) && !empty($extra_types)) {
-        $modules = $this->reOrderModulesForAlter($modules, $hook);
+      if (isset($extra_types) && !$extra_types) {
+        $modules = array_keys(array_intersect_key($extra_listeners, $hook_listeners));
+      }
+      else {
+        $modules = array_keys($hook_listeners);
+        if ($extra_modules) {
+          $modules = $this->reOrderModulesForAlter($modules, $hook);
+        }
       }
       foreach ($modules as $module) {
         foreach ($hook_listeners[$module] ?? [] as $listener) {
@@ -595,4 +589,19 @@ protected function getHookListeners(string $hook): array {
     return $this->invokeMap[$hook] ?? [];
   }
 
+  public function findListenersForAlter($hook, array $hook_listeners = [], ?bool &$extra_modules = NULL): array {
+    foreach ($this->getHookListeners($hook) as $module => $listeners) {
+      if (isset($hook_listeners[$module])) {
+        $hook_listeners[$module] = array_merge($hook_listeners[$module], $listeners);
+      }
+      else {
+        $hook_listeners[$module] = $listeners;
+        // It is used below.
+        // @phpcs:ignore DrupalPractice.CodeAnalysis.VariableAnalysis.UnusedVariable
+        $extra_modules = TRUE;
+      }
+    }
+    return $hook_listeners;
+  }
+
 }
-- 
GitLab


From 536eba0178f44e8f3bf17b2624555c66473be466 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Fri, 13 Dec 2024 22:19:09 -0500
Subject: [PATCH 050/173] Comments

---
 .../Drupal/Core/Extension/ModuleHandler.php   | 19 +++++++++++++++++--
 .../Drupal/Core/Hook/Attribute/HookAfter.php  |  4 ++--
 .../Drupal/Core/Hook/Attribute/HookBefore.php |  4 ++--
 .../Core/Hook/Attribute/HookOrderBase.php     |  3 +++
 .../Core/Hook/Attribute/HookOrderGroup.php    |  8 ++++----
 .../Drupal/Core/Hook/HookCollectorPass.php    |  9 ++++++++-
 core/lib/Drupal/Core/Hook/HookPriority.php    |  6 +++++-
 .../Tests/ckEditor5CodeSyntaxTest.js          |  1 -
 8 files changed, 41 insertions(+), 13 deletions(-)

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 207db740edca..bd29c846d058 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -444,6 +444,8 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
         foreach (array_merge($extra_hooks, [$type . '_alter']) as $extra_hook) {
           if (isset($this->hooksOrderedByAttributes[$extra_hook])) {
             $group = $this->hooksOrderedByAttributes[$extra_hook];
+            // When checking for already ordered groups ensure the listener
+            // is in the same order as when we set it.
             krsort($group);
             $extra_listeners = $this->findListenersForAlter(implode(':', $group));
             $extra_types = array_diff($extra_hooks, $group);
@@ -589,14 +591,27 @@ protected function getHookListeners(string $hook): array {
     return $this->invokeMap[$hook] ?? [];
   }
 
-  public function findListenersForAlter($hook, array $hook_listeners = [], ?bool &$extra_modules = NULL): array {
+  /**
+   * Helper to get hook listeners when in alter.
+   *
+   * @param string $hook
+   *   The extra hook or combination hook to check for.
+   * @param array $hook_listeners
+   *   Hook listeners for the current hook_alter.
+   * @param bool $extra_modules
+   *   Whether there are extra modules to order.
+   *
+   * @return array
+   *   The hook listeners.
+   */
+  public function findListenersForAlter(string $hook, array $hook_listeners = [], ?bool &$extra_modules = NULL): array {
     foreach ($this->getHookListeners($hook) as $module => $listeners) {
       if (isset($hook_listeners[$module])) {
         $hook_listeners[$module] = array_merge($hook_listeners[$module], $listeners);
       }
       else {
         $hook_listeners[$module] = $listeners;
-        // It is used below.
+        // It is used by reference.
         // @phpcs:ignore DrupalPractice.CodeAnalysis.VariableAnalysis.UnusedVariable
         $extra_modules = TRUE;
       }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
index 80199bc78c09..dd7de1b3ceab 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
@@ -7,7 +7,7 @@
 /**
  * Attribute for marking that a hook's order should be changed.
  *
- * This allows you to ensure the hook is executed after
+ * This allows you to ensure the hook is executed after similar
  * hooks in other modules.
  *
  * @section sec_backwards_compatibility Backwards-compatibility
@@ -27,7 +27,7 @@ class HookAfter extends HookOrderBase {
    * Constructs a HookAfter attribute.
    *
    * @param array $modules
-   *   The module this implementation should run before.
+   *   The modules this implementation should run before.
    */
   public function __construct(
     public array $modules,
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
index e93daa0b83cd..6ab83a3e4c83 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
@@ -7,7 +7,7 @@
 /**
  * Attribute for marking that a hook's order should be changed.
  *
- * This allows you to ensure the hook is executed before
+ * This allows you to ensure the hook is executed before similar
  * hooks in other modules.
  *
  * @section sec_backwards_compatibility Backwards-compatibility
@@ -27,7 +27,7 @@ class HookBefore extends HookOrderBase {
    * Constructs a HookBefore lib/Drupal/Core/Hook/Attribute/attribute.
    *
    * @param array $modules
-   *   The module this implementation should run before.
+   *   The modules this implementation should run before.
    */
   public function __construct(
     public array $modules,
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
index 9d482e18c3d1..4a657bd8497c 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
@@ -39,6 +39,9 @@ class HookOrderBase implements HookOrderInterface {
 
   /**
    * Constructs a HookOrderBase class.
+   *
+   * @param bool $shouldBeLarger
+   *   Determines whether the hook should increase or decrease priority.
    */
   public function __construct(public readonly bool $shouldBeLarger) {}
 
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php
index 346a78ee7e67..7b31153235ec 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php
@@ -7,8 +7,8 @@
 /**
  * Attribute for marking which specific implementations to group.
  *
- * This allows hook ordering to handle extra types such as ordering form_alter
- * relative to hook_form_FORM_ID_alter.
+ * This allows hook ordering to handle extra types such as ordering
+ * hook_form_alter relative to hook_form_FORM_ID_alter.
  *
  * @section sec_backwards_compatibility Backwards-compatibility
  *
@@ -24,13 +24,13 @@
 class HookOrderGroup {
 
   /**
-   * Constructs a core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php attribute object.
+   * Constructs a HookOrderGroup attribute object.
    *
    * @param array $group
    *   A list of hooks to sort together. For example, if a method implementing
    *   form_BASE_FORM_ID_alter wants to sort itself relative to some
    *   implementations of form_FORM_ID_alter then this would contain those.
-   *   See Ckeditor5::formFilterFormatFormAlter() for example.
+   *   See Ckeditor5::formFilterFormatFormAlter() for an example.
    */
   public function __construct(
     public array $group,
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index c3ebb3ad0df9..7b642cbe256f 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -116,7 +116,7 @@ public function process(ContainerBuilder $container): array {
     }
     $orderGroups = array_map('array_unique', $orderGroups);
 
-    // This can be removed when ModuleHandler::add() is removed.
+    // @todo remove if statement wrapper when ModuleHandler::add() is removed.
     if (count($container->getDefinitions()) > 1) {
       static::registerServices($container, $collector, $implementations, $legacyImplementations ?? [], $orderGroups);
       static::reOrderServices($container, $allOrderAttributes, $orderGroups, $implementations);
@@ -125,6 +125,11 @@ public function process(ContainerBuilder $container): array {
   }
 
   /**
+   * Register hook implementations as event listeners.
+   *
+   * Passes required include information to module_handler.
+   * Passes required runtime ordering information to module_handler.
+   *
    * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
    *   The container.
    * @param \Drupal\Core\Hook\HookCollectorPass $collector
@@ -194,6 +199,8 @@ protected static function registerServices(ContainerBuilder $container, HookColl
   }
 
   /**
+   * Reorder services that have attributes specifying an order.
+   *
    * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
    *   The container.
    * @param array $allOrderAttributes
diff --git a/core/lib/Drupal/Core/Hook/HookPriority.php b/core/lib/Drupal/Core/Hook/HookPriority.php
index 4187918da4a9..c712aa4ed634 100644
--- a/core/lib/Drupal/Core/Hook/HookPriority.php
+++ b/core/lib/Drupal/Core/Hook/HookPriority.php
@@ -7,6 +7,9 @@
 use Drupal\Core\Hook\Attribute\HookOrderBase;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 
+/**
+ * A class to handle updating priority of hook listeners.
+ */
 class HookPriority {
 
   public function __construct(protected ContainerBuilder $container) {}
@@ -20,7 +23,7 @@ public function __construct(protected ContainerBuilder $container) {}
    *   The order attribute.
    * @param array|null $others
    *   Other hook implementations to compare to, if any. The array is a list of
-   *   arrays containing a class, a method and module.
+   *   arrays containing a class, a method, and module.
    */
   public function change(array $hooks, HookOrderBase $attribute, ?array $others = NULL): void {
     $class_and_method = "$attribute->class::$attribute->method";
@@ -29,6 +32,7 @@ public function change(array $hooks, HookOrderBase $attribute, ?array $others =
     }
     if (count($hooks) > 1) {
       $map = $this->container->getParameter('hook_implementations_map');
+      // Order the complex listener so we can find it runtime.
       krsort($hooks);
       $combinedHookTag = implode(':', $hooks);
       $event = "drupal_hook.$combinedHookTag";
diff --git a/core/modules/ckeditor5/tests/src/Nightwatch/Tests/ckEditor5CodeSyntaxTest.js b/core/modules/ckeditor5/tests/src/Nightwatch/Tests/ckEditor5CodeSyntaxTest.js
index 42cc0a335f90..2bf3247862c7 100644
--- a/core/modules/ckeditor5/tests/src/Nightwatch/Tests/ckEditor5CodeSyntaxTest.js
+++ b/core/modules/ckeditor5/tests/src/Nightwatch/Tests/ckEditor5CodeSyntaxTest.js
@@ -43,7 +43,6 @@ module.exports = {
         // Wait for new source editing vertical tab to be present before continuing.
         .waitForElementVisible(
           '[href*=edit-editor-settings-plugins-ckeditor5-sourceediting]',
-          9000,
         )
         .click('.ckeditor5-toolbar-item-codeBlock') // Select the Code Block button.
         // Hit the down arrow key to move it to the toolbar.
-- 
GitLab


From a26259fdc7458d3bdf125dd018f97003107518e4 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Fri, 13 Dec 2024 22:28:03 -0500
Subject: [PATCH 051/173] Add comment to test modules

---
 .../src/Hook/FirstAlphabeticallyHooks1.php         | 13 ++++++++++++-
 .../src/Hook/FirstAlphabeticallyHooks2.php         | 13 ++++++++++++-
 .../src/Hook/FirstAlphabeticallyHooks3.php         | 13 ++++++++++++-
 .../src/Hook/FirstAlphabeticallyHooks4.php         | 14 +++++++++++++-
 .../src/Hook/FirstAlphabeticallyHooks5.php         | 13 ++++++++++++-
 .../src/Hook/LastAlphabeticallyHooks1.php          | 13 ++++++++++++-
 .../src/Hook/LastAlphabeticallyHooks2.php          | 13 ++++++++++++-
 .../src/Hook/LastAlphabeticallyHooks3.php          | 13 ++++++++++++-
 .../src/Hook/LastAlphabeticallyHooks4.php          | 14 +++++++++++++-
 .../src/Hook/LastAlphabeticallyHooks5.php          | 13 ++++++++++++-
 10 files changed, 122 insertions(+), 10 deletions(-)

diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php b/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php
index e61ba8bc229c..6cc3477c5a14 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php
@@ -7,7 +7,18 @@
 use Drupal\Core\Hook\Attribute\Hook;
 
 /**
- * Hook implementations for hook_order_first_alphabetically.
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. All of these modules
+ * come in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each pair tests one hook order attribute.
+ *
+ * This pair tests #[HookFirst].
  */
 class FirstAlphabeticallyHooks1 {
 
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php b/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php
index c0c2f254d337..341688ce600d 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php
@@ -8,7 +8,18 @@
 use Drupal\Core\Hook\Attribute\HookAfter;
 
 /**
- * Hook implementations for hook_order_first_alphabetically.
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. All of these modules
+ * come in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each pair tests one hook order attribute.
+ *
+ * This pair tests #[HookAfter].
  */
 class FirstAlphabeticallyHooks2 {
 
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically3/src/Hook/FirstAlphabeticallyHooks3.php b/core/modules/system/tests/modules/hook_order_first_alphabetically3/src/Hook/FirstAlphabeticallyHooks3.php
index dc84d9c8fef2..eeb79e8e630c 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically3/src/Hook/FirstAlphabeticallyHooks3.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically3/src/Hook/FirstAlphabeticallyHooks3.php
@@ -7,7 +7,18 @@
 use Drupal\Core\Hook\Attribute\Hook;
 
 /**
- * Hook implementations for hook_order_first_alphabetically.
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. All of these modules
+ * come in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each pair tests one hook order attribute.
+ *
+ * This pair tests #[HookBefore].
  */
 class FirstAlphabeticallyHooks3 {
 
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically4/src/Hook/FirstAlphabeticallyHooks4.php b/core/modules/system/tests/modules/hook_order_first_alphabetically4/src/Hook/FirstAlphabeticallyHooks4.php
index 84c2c1e001c1..4b7778bc5f0b 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically4/src/Hook/FirstAlphabeticallyHooks4.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically4/src/Hook/FirstAlphabeticallyHooks4.php
@@ -9,7 +9,19 @@
 use Drupal\Core\Hook\Attribute\HookOrderGroup;
 
 /**
- * Hook implementations for hook_order_first_alphabetically.
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. All of these modules
+ * come in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each pair tests one hook order attribute.
+ *
+ * This pair tests #[HookOrderGroup].
+ * This attribute must be paired with #[HookAfter] or #[HookBefore].
  */
 class FirstAlphabeticallyHooks4 {
 
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically5/src/Hook/FirstAlphabeticallyHooks5.php b/core/modules/system/tests/modules/hook_order_first_alphabetically5/src/Hook/FirstAlphabeticallyHooks5.php
index 8b687c359738..540cd86c604c 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically5/src/Hook/FirstAlphabeticallyHooks5.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically5/src/Hook/FirstAlphabeticallyHooks5.php
@@ -8,7 +8,18 @@
 use Drupal\Core\Hook\Attribute\HookLast;
 
 /**
- * Hook implementations for hook_order_first_alphabetically.
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. All of these modules
+ * come in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each pair tests one hook order attribute.
+ *
+ * This pair tests #[HookLast].
  */
 class FirstAlphabeticallyHooks5 {
 
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php b/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php
index 83ee0b3b09d1..6a55234725d9 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php
@@ -9,7 +9,18 @@
 use Drupal\Core\Hook\Attribute\HookOrderGroup;
 
 /**
- * Hook implementations for hook_order_last_alphabetically.
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. All of these modules
+ * come in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each pair tests one hook order attribute.
+ *
+ * This pair tests #[HookFirst].
  */
 class LastAlphabeticallyHooks1 {
 
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php b/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php
index 4210d9364608..02df5b541c46 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php
@@ -7,7 +7,18 @@
 use Drupal\Core\Hook\Attribute\Hook;
 
 /**
- * Hook implementations for hook_order_last_alphabetically.
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. All of these modules
+ * come in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each pair tests one hook order attribute.
+ *
+ * This pair tests #[HookAfter].
  */
 class LastAlphabeticallyHooks2 {
 
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically3/src/Hook/LastAlphabeticallyHooks3.php b/core/modules/system/tests/modules/hook_order_last_alphabetically3/src/Hook/LastAlphabeticallyHooks3.php
index 3f985067ba9b..676f897a93a7 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically3/src/Hook/LastAlphabeticallyHooks3.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically3/src/Hook/LastAlphabeticallyHooks3.php
@@ -9,7 +9,18 @@
 use Drupal\Core\Hook\Attribute\HookOrderGroup;
 
 /**
- * Hook implementations for hook_order_last_alphabetically3.
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. All of these modules
+ * come in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each pair tests one hook order attribute.
+ *
+ * This pair tests #[HookBefore].
  */
 class LastAlphabeticallyHooks3 {
 
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically4/src/Hook/LastAlphabeticallyHooks4.php b/core/modules/system/tests/modules/hook_order_last_alphabetically4/src/Hook/LastAlphabeticallyHooks4.php
index 37b36995e5f6..cc7244a9eeb6 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically4/src/Hook/LastAlphabeticallyHooks4.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically4/src/Hook/LastAlphabeticallyHooks4.php
@@ -7,7 +7,19 @@
 use Drupal\Core\Hook\Attribute\Hook;
 
 /**
- * Hook implementations for hook_order_last_alphabetically.
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. All of these modules
+ * come in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each pair tests one hook order attribute.
+ *
+ * This pair tests #[HookOrderGroup].
+ * This attribute must be paired with #[HookAfter] or #[HookBefore].
  */
 class LastAlphabeticallyHooks4 {
 
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically5/src/Hook/LastAlphabeticallyHooks5.php b/core/modules/system/tests/modules/hook_order_last_alphabetically5/src/Hook/LastAlphabeticallyHooks5.php
index cc6b117e4a9d..ae9fbfd24a7e 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically5/src/Hook/LastAlphabeticallyHooks5.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically5/src/Hook/LastAlphabeticallyHooks5.php
@@ -7,7 +7,18 @@
 use Drupal\Core\Hook\Attribute\Hook;
 
 /**
- * Hook implementations for hook_order_last_alphabetically.
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. All of these modules
+ * come in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each pair tests one hook order attribute.
+ *
+ * This pair tests #[HookLast].
  */
 class LastAlphabeticallyHooks5 {
 
-- 
GitLab


From e29ff98fd354ffdfd6e6e6c4edc05dc1a3351624 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Fri, 13 Dec 2024 22:32:59 -0500
Subject: [PATCH 052/173] Clean up test modules

---
 .../src/Hook/LastAlphabeticallyHooks1.php                       | 2 --
 .../src/Hook/LastAlphabeticallyHooks3.php                       | 2 --
 2 files changed, 4 deletions(-)

diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php b/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php
index 6a55234725d9..059e95d0f4c1 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php
@@ -6,7 +6,6 @@
 
 use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\Core\Hook\Attribute\HookFirst;
-use Drupal\Core\Hook\Attribute\HookOrderGroup;
 
 /**
  * Hook implementations for verifying ordering hooks by attributes.
@@ -29,7 +28,6 @@ class LastAlphabeticallyHooks1 {
    */
   #[HookFirst]
   #[Hook('cache_flush')]
-  #[HookOrderGroup(['cache_flush'])]
   public static function cacheFlush(): void {
     $GLOBALS['HookFirst'] = 'HookFirst';
   }
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically3/src/Hook/LastAlphabeticallyHooks3.php b/core/modules/system/tests/modules/hook_order_last_alphabetically3/src/Hook/LastAlphabeticallyHooks3.php
index 676f897a93a7..a25ac5454247 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically3/src/Hook/LastAlphabeticallyHooks3.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically3/src/Hook/LastAlphabeticallyHooks3.php
@@ -6,7 +6,6 @@
 
 use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\Core\Hook\Attribute\HookBefore;
-use Drupal\Core\Hook\Attribute\HookOrderGroup;
 
 /**
  * Hook implementations for verifying ordering hooks by attributes.
@@ -28,7 +27,6 @@ class LastAlphabeticallyHooks3 {
    * Before FirstAlphabeticallyHooks3::cacheFlush.
    */
   #[HookBefore(['hook_order_first_alphabetically3'])]
-  #[HookOrderGroup(['cache_flush'])]
   #[Hook('cache_flush')]
   public static function cacheFlush(): void {
     $GLOBALS['HookBefore'] = 'HookBefore';
-- 
GitLab


From 114b7c5a9a203ac68d79b2c58beb65a935254bb4 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Fri, 13 Dec 2024 22:36:56 -0500
Subject: [PATCH 053/173] Add comments to tests

---
 .../src/Hook/FirstAlphabeticallyHooks1.php                       | 1 +
 .../src/Hook/FirstAlphabeticallyHooks3.php                       | 1 +
 .../src/Hook/FirstAlphabeticallyHooks4.php                       | 1 +
 .../src/Hook/LastAlphabeticallyHooks5.php                        | 1 +
 4 files changed, 4 insertions(+)

diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php b/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php
index 6cc3477c5a14..f197cea8d043 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php
@@ -27,6 +27,7 @@ class FirstAlphabeticallyHooks1 {
    */
   #[Hook('cache_flush')]
   public static function cacheFlush(): void {
+    // This should be run after so HookFirst should not be set.
     if (!isset($GLOBALS['HookFirst'])) {
       $GLOBALS['HookOutOfOrderTestingFirst'] = 'HookOutOfOrderTestingFirst';
     }
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically3/src/Hook/FirstAlphabeticallyHooks3.php b/core/modules/system/tests/modules/hook_order_first_alphabetically3/src/Hook/FirstAlphabeticallyHooks3.php
index eeb79e8e630c..479c7d85d149 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically3/src/Hook/FirstAlphabeticallyHooks3.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically3/src/Hook/FirstAlphabeticallyHooks3.php
@@ -27,6 +27,7 @@ class FirstAlphabeticallyHooks3 {
    */
   #[Hook('cache_flush')]
   public static function cacheFlush(): void {
+    // This should be run after so HookBefore should not be set.
     if (!isset($GLOBALS['HookBefore'])) {
       $GLOBALS['HookOutOfOrderTestingBefore'] = 'HookOutOfOrderTestingBefore';
     }
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically4/src/Hook/FirstAlphabeticallyHooks4.php b/core/modules/system/tests/modules/hook_order_first_alphabetically4/src/Hook/FirstAlphabeticallyHooks4.php
index 4b7778bc5f0b..08eef7984c09 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically4/src/Hook/FirstAlphabeticallyHooks4.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically4/src/Hook/FirstAlphabeticallyHooks4.php
@@ -32,6 +32,7 @@ class FirstAlphabeticallyHooks4 {
   #[HookOrderGroup(['custom_hook_extra_types2_alter'])]
   #[Hook('custom_hook_extra_types1_alter')]
   public static function customHookExtraTypes(): void {
+    // This should be run after so HookOrderGroupExtraTypes should not be set.
     if (!isset($GLOBALS['HookOrderGroupExtraTypes'])) {
       $GLOBALS['HookOutOfOrderTestingOrderGroupsExtraTypes'] = 'HookOutOfOrderTestingOrderGroupsExtraTypes';
     }
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically5/src/Hook/LastAlphabeticallyHooks5.php b/core/modules/system/tests/modules/hook_order_last_alphabetically5/src/Hook/LastAlphabeticallyHooks5.php
index ae9fbfd24a7e..75a0dbe4c785 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically5/src/Hook/LastAlphabeticallyHooks5.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically5/src/Hook/LastAlphabeticallyHooks5.php
@@ -27,6 +27,7 @@ class LastAlphabeticallyHooks5 {
    */
   #[Hook('cache_flush')]
   public static function cacheFlush(): void {
+    // This should be run before so HookLast should not be set.
     if (isset($GLOBALS['HookLast'])) {
       $GLOBALS['HookOutOfOrderTestingLast'] = 'HookOutOfOrderTestingLast';
     }
-- 
GitLab


From 175713d42d746ee9ab4a5a4e31d334acbd500678 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Sat, 14 Dec 2024 10:14:36 -0500
Subject: [PATCH 054/173] Documentation updates

---
 .../Drupal/Core/Extension/ModuleHandler.php   |  22 ++--
 .../Drupal/Core/Hook/Attribute/HookAfter.php  |  13 +--
 .../Drupal/Core/Hook/Attribute/HookBefore.php |  13 +--
 .../Drupal/Core/Hook/Attribute/HookFirst.php  |  12 +-
 .../Drupal/Core/Hook/Attribute/HookLast.php   |  12 +-
 .../Core/Hook/Attribute/HookOrderGroup.php    |  27 +++--
 .../Hook/Attribute/HookOrderInterface.php     |   3 +-
 .../Drupal/Core/Hook/HookCollectorPass.php    | 107 +++++++++++-------
 core/lib/Drupal/Core/Hook/HookPriority.php    |  13 ++-
 9 files changed, 117 insertions(+), 105 deletions(-)

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index bd29c846d058..712b7b3b4d0b 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -434,13 +434,14 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
       $extra_listeners = [];
       if (isset($extra_types)) {
         $extra_hooks = array_map(fn ($x) => $x . '_alter', $extra_types);
+        // First get the listeners implementing extra hooks.
         foreach ($extra_hooks as $extra_hook) {
           $hook_listeners = $this->findListenersForAlter($extra_hook, $hook_listeners, $extra_modules);
         }
-        // For multiple hooks, we need $modules to contain every module that
-        // implements at least one of them in the correct order. Hooks already
-        // ordered by attributes are also ordered by
-        // hook_module_implements_alter() they don't need to be ordered again.
+        // Second, gather implementations defined in a
+        // Drupal\Core\Hook\Attribute\HookOrderGroup attribute. These are only
+        // used for ordering because the group might contain hooks not included
+        // in this alter() call.
         foreach (array_merge($extra_hooks, [$type . '_alter']) as $extra_hook) {
           if (isset($this->hooksOrderedByAttributes[$extra_hook])) {
             $group = $this->hooksOrderedByAttributes[$extra_hook];
@@ -448,20 +449,21 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
             // is in the same order as when we set it.
             krsort($group);
             $extra_listeners = $this->findListenersForAlter(implode(':', $group));
+            // Remove already ordered hooks.
             $extra_types = array_diff($extra_hooks, $group);
           }
         }
       }
-      // 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.
-      if (isset($extra_types) && !$extra_types) {
+      // If multiple alters were called, but they were already ordered by
+      // ordering attributes then keep that order.
+      if (isset($extra_types) && empty($extra_types)) {
         $modules = array_keys(array_intersect_key($extra_listeners, $hook_listeners));
       }
       else {
+        // Otherwise, use a legacy ordering mechanism if needed.
         $modules = array_keys($hook_listeners);
         if ($extra_modules) {
-          $modules = $this->reOrderModulesForAlter($modules, $hook);
+          $modules = $this->legacyReOrderModulesForAlter($modules, $hook);
         }
       }
       foreach ($modules as $module) {
@@ -487,7 +489,7 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
    *   The list, potentially reordered and changed by
    *   hook_module_implements_alter().
    */
-  protected function reOrderModulesForAlter(array $modules, string $hook): array {
+  protected function legacyReOrderModulesForAlter(array $modules, string $hook): array {
     // Order by module order first.
     $modules = array_intersect(array_keys($this->moduleList), $modules);
     // Alter expects the module list to be in the keys.
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
index dd7de1b3ceab..01a68e6f5e59 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
@@ -5,20 +5,11 @@
 namespace Drupal\Core\Hook\Attribute;
 
 /**
- * Attribute for marking that a hook's order should be changed.
- *
- * This allows you to ensure the hook is executed after similar
- * hooks in other modules.
+ * Attribute to request this hook implementation to fire after others.
  *
  * @section sec_backwards_compatibility Backwards-compatibility
  *
- * To allow hook implementations to work on older versions of Drupal as well,
- * keep the hook_module_implements_alter implementation and add the appropriate
- * combination of #[HookFirst], #[HookLast], #[HookBefore], #[HookAfter], and
- * #[HookOrderGroup] attributes. Then ensure you have added #[LegacyHook] to
- * the hook_module_implements_alter() implementation.
- *
- * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
+ * @see HookOrderGroup
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
 class HookAfter extends HookOrderBase {
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
index 6ab83a3e4c83..ee87e431418c 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
@@ -5,20 +5,11 @@
 namespace Drupal\Core\Hook\Attribute;
 
 /**
- * Attribute for marking that a hook's order should be changed.
- *
- * This allows you to ensure the hook is executed before similar
- * hooks in other modules.
+ * Attribute to request this hook implementation to fire before others.
  *
  * @section sec_backwards_compatibility Backwards-compatibility
  *
- * To allow hook implementations to work on older versions of Drupal as well,
- * keep the hook_module_implements_alter implementation and add the appropriate
- * combination of #[HookFirst], #[HookLast], #[HookBefore], #[HookAfter], and
- * #[HookOrderGroup] attributes. Then ensure you have added #[LegacyHook] to
- * the hook_module_implements_alter() implementation.
- *
- * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
+ * @see HookOrderGroup
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
 class HookBefore extends HookOrderBase {
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php b/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
index 45f98cd6d87b..b7d82aab5d13 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
@@ -5,19 +5,11 @@
 namespace Drupal\Core\Hook\Attribute;
 
 /**
- * Attribute for marking that a hook should be executed first.
- *
- * This makes sure that this hook runs before all other hooks of the same type.
+ * Attribute to request this hook implementation to fire first.
  *
  * @section sec_backwards_compatibility Backwards-compatibility
  *
- * To allow hook implementations to work on older versions of Drupal as well,
- * keep the hook_module_implements_alter implementation and add the appropriate
- * combination of #[HookFirst], #[HookLast], #[HookBefore], #[HookAfter], and
- * #[HookOrderGroup] attributes. Then ensure you have added #[LegacyHook] to
- * the hook_module_implements_alter() implementation.
- *
- * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
+ * @see HookOrderGroup
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
 class HookFirst extends HookOrderBase {
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookLast.php b/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
index 759b6df2e301..d5c92d00eb77 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
@@ -5,19 +5,11 @@
 namespace Drupal\Core\Hook\Attribute;
 
 /**
- * Attribute for marking that a hook should be executed last.
- *
- * This makes sure that this hook runs after all other hooks of the same type.
+ * Attribute to request this hook implementation to fire after others.
  *
  * @section sec_backwards_compatibility Backwards-compatibility
  *
- * To allow hook implementations to work on older versions of Drupal as well,
- * keep the hook_module_implements_alter implementation and add the appropriate
- * combination of #[HookFirst], #[HookLast], #[HookBefore], #[HookAfter], and
- * #[HookOrderGroup] attributes. Then ensure you have added #[LegacyHook] to
- * the hook_module_implements_alter() implementation.
- *
- * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
+ * @see HookOrderGroup
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
 class HookLast extends HookOrderBase {
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php
index 7b31153235ec..0e0b43044af6 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php
@@ -5,18 +5,26 @@
 namespace Drupal\Core\Hook\Attribute;
 
 /**
- * Attribute for marking which specific implementations to group.
+ * List of alter hooks called together.
  *
- * This allows hook ordering to handle extra types such as ordering
- * hook_form_alter relative to hook_form_FORM_ID_alter.
+ * Ordering by attributes happens at build time by setting up the order of
+ * the listeners of a hook correctly. However, ModuleHandlerInterface::alter()
+ * can be called with multiple hooks runtime. If the hook defined on this
+ * method/class requires ordering relative to other such hooks then this
+ * attribute can be used to order relative to implementations of all hooks in
+ * the group. Include all alter hooks to be ordered against in the group even
+ * if no single alter() call includes all of them. For example, this can be
+ * used to order a hook_form_BASE_FORM_ID_alter() implementation relative to
+ * multiple hook_form_FORM_ID_alter() implementations as
+ * Drupal\ckeditor5\Hook\Ckeditor5Hooks::formFilterFormatFormAlter() does.
  *
  * @section sec_backwards_compatibility Backwards-compatibility
  *
  * To allow hook implementations to work on older versions of Drupal as well,
- * keep the hook_module_implements_alter implementation and add the appropriate
- * combination of #[HookFirst], #[HookLast], #[HookBefore], #[HookAfter], and
- * #[HookOrderGroup] attributes. Then ensure you have added #[LegacyHook] to
- * the hook_module_implements_alter() implementation.
+ * keep the hook_module_implements_alter() implementation and execute the
+ * same ordering as prescribed by the hook order attributes. Then add
+ * #[LegacyHook] to the hook_module_implements_alter() implementation so it
+ * only gets executed in older Drupal versions.
  *
  * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
  */
@@ -27,10 +35,7 @@ class HookOrderGroup {
    * Constructs a HookOrderGroup attribute object.
    *
    * @param array $group
-   *   A list of hooks to sort together. For example, if a method implementing
-   *   form_BASE_FORM_ID_alter wants to sort itself relative to some
-   *   implementations of form_FORM_ID_alter then this would contain those.
-   *   See Ckeditor5::formFilterFormatFormAlter() for an example.
+   *   A list of hooks to sort together.
    */
   public function __construct(
     public array $group,
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
index 2e70e18f153f..1c87e3b51a08 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
@@ -19,7 +19,8 @@ interface HookOrderInterface {
    * @param string $method
    *   The method of the hook.
    * @param string $module
-   *   The module of the hook.
+   *   The module of the hook. Note this might be different from the module the
+   *   class is in.
    */
   public function set(string $hook, string $class, string $method, string $module): static;
 
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 7b642cbe256f..aeaec41fd46e 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -58,7 +58,14 @@ class HookCollectorPass implements CompilerPassInterface {
   private array $groupIncludes = [];
 
   /**
-   * A list of attributes in modules for Hooks.
+   * A list of attributes for hook implementations.
+   *
+   * Keys are module, class and method. Values are all possible attributes on
+   * hook implementations: Hook to define the hook, HookOrderInterface to
+   * define the order in which the implementations fire, HookOrderGroup to
+   * define a group of hooks to be ordered together.
+   *
+   * @var array<string, <array string, <array string, Hook|HookOrderInterface|HookOrderGroup>>>
    */
   protected array $moduleAttributes = [];
 
@@ -78,31 +85,31 @@ public function process(ContainerBuilder $container): array {
           $orderGroup = FALSE;
           $hook = FALSE;
           foreach ($attributes as $attribute) {
-            if ($attribute instanceof Hook) {
-              if ($class !== ProceduralCall::class) {
-                self::checkForProceduralOnlyHooks($attribute, $class);
-              }
-              $hook = $attribute->hook;
-              $hookModule = $attribute->module ?: $module;
-              if ($attribute->method) {
-                $method = $attribute->method;
-              }
-              $legacyImplementations[$hook][$hookModule] = '';
-              $implementations[$hook][$hookModule][$class][] = $method;
-            }
-            if ($attribute instanceof HookOrderInterface) {
-              $orderAttributes[] = $attribute;
-            }
-            if ($attribute instanceof HookOrderGroup) {
-              $orderGroup = $attribute->group;
+            switch (TRUE) {
+              case $attribute instanceof Hook:
+                if ($class !== ProceduralCall::class) {
+                  self::checkForProceduralOnlyHooks($attribute, $class);
+                }
+                $hook = $attribute->hook;
+                if ($attribute->module) {
+                  $module = $attribute->module;
+                }
+                $legacyImplementations[$hook][$module] = '';
+                $implementations[$hook][$module][$class][] = $attribute->method ?: $method;
+                break;
+
+              case $attribute instanceof HookOrderInterface:
+                $orderAttributes[] = $attribute;
+                break;
+
+              case $attribute instanceof HookOrderGroup:
+                $orderGroup = $attribute->group;
+                break;
             }
           }
           if ($hook) {
             foreach ($orderAttributes as $orderAttribute) {
-              // $hookModule is set in the same clause as $hook
-              // if $hook is set then $hookModule is.
-              /** @phpstan-ignore variable.undefined */
-              $allOrderAttributes[] = $orderAttribute->set(hook: $hook, class: $class, method: $method, module: $hookModule);
+              $allOrderAttributes[] = $orderAttribute->set(hook: $hook, class: $class, method: $method, module: $module);
             }
             if ($orderGroup) {
               $orderGroup[] = $hook;
@@ -116,7 +123,9 @@ public function process(ContainerBuilder $container): array {
     }
     $orderGroups = array_map('array_unique', $orderGroups);
 
-    // @todo remove if statement wrapper when ModuleHandler::add() is removed.
+    // @todo investigate whether this if() is needed after ModuleHandler::add()
+    // is removed.
+    // @see https://www.drupal.org/project/drupal/issues/3481778
     if (count($container->getDefinitions()) > 1) {
       static::registerServices($container, $collector, $implementations, $legacyImplementations ?? [], $orderGroups);
       static::reOrderServices($container, $allOrderAttributes, $orderGroups, $implementations);
@@ -253,8 +262,8 @@ protected static function reOrderServices(ContainerBuilder $container, array $al
    * @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
+   *   @see https://www.drupal.org/project/drupal/issues/3481778
    */
   public static function collectAllHookImplementations(array $module_filenames, ?ContainerBuilder $container = NULL): static {
     $modules = array_map(fn ($x) => preg_quote($x, '/'), array_keys($module_filenames));
@@ -317,14 +326,9 @@ protected function collectModuleHookImplementations($dir, $module, $module_preg,
           $attributes = [];
           if (class_exists($class)) {
             $reflectionClass = new \ReflectionClass($class);
-            if ($class_attributes = $reflectionClass->getAttributes()) {
-              $attributes['__invoke'] = array_map(fn ($x) => $x->newInstance(), $class_attributes);
-            }
-            foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $methodReflection) {
-              if ($method_attributes = $methodReflection->getAttributes()) {
-                $attributes[$methodReflection->getName()] = array_map(fn ($x) => $x->newInstance(), $method_attributes);
-              }
-            }
+            $reflections = $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC);
+            $reflections[] = $reflectionClass;
+            $attributes = self::getAttributeInstances($attributes, $reflections);
             $hook_file_cache->set($filename, ['class' => $class, 'attributes' => $attributes]);
           }
         }
@@ -385,14 +389,14 @@ protected static function filterIterator(\SplFileInfo $fileInfo, $key, \Recursiv
    *   The file this procedural implementation is in.
    * @param string $hook
    *   The name of the hook.
-   * @param string $hookModule
-   *   The name of the module this hook implementation belongs to. It can be
-   *   different to the file where $function is in.
+   * @param string $module
+   *   The module of the hook. Note this might be different from the module the
+   *   function is in.
    * @param string $function
    *   The name of function implementing the hook.
    */
-  protected function addProceduralImplementation(\SplFileInfo $fileinfo, string $hook, string $hookModule, string $function): void  {
-    $this->moduleAttributes[$hookModule][ProceduralCall::class][$function] = [new Hook($hook, $hookModule . '_' . $hook)];
+  protected function addProceduralImplementation(\SplFileInfo $fileinfo, string $hook, string $module, string $function): void {
+    $this->moduleAttributes[$module][ProceduralCall::class][$function] = [new Hook($hook, $module . '_' . $hook)];
     if ($hook === 'hook_info') {
       $this->hookInfo[] = $function;
     }
@@ -407,6 +411,9 @@ protected function addProceduralImplementation(\SplFileInfo $fileinfo, string $h
   /**
    * This method is only to be used by ModuleHandler.
    *
+   * @todo remove when ModuleHandler::add() is removed.
+   * @see https://www.drupal.org/project/drupal/issues/3481778
+   *
    * @internal
    */
   public function loadAllIncludes(): void {
@@ -418,6 +425,9 @@ public function loadAllIncludes(): void {
   /**
    * This method is only to be used by ModuleHandler.
    *
+   * @todo remove when ModuleHandler::add() is removed.
+   * @see https://www.drupal.org/project/drupal/issues/3481778
+   *
    * @internal
    */
   public function getImplementations($paths): array {
@@ -452,4 +462,25 @@ public static function checkForProceduralOnlyHooks(Hook $hook, string $class): v
     }
   }
 
+  /**
+   * Get attribute instances from class and method reflections.
+   *
+   * @param array $attributes
+   *   The current attributes.
+   * @param array $reflections
+   *   A list of class and method reflections.
+   *
+   * @return array
+   *   A list of Hook|HookOrderInterface|HookOrderGroup attribute instances.
+   */
+  protected static function getAttributeInstances(array $attributes, array $reflections): array {
+    foreach ($reflections as $reflection) {
+      if ($reflection_attributes = $reflection->getAttributes()) {
+        $method = $reflection instanceof \ReflectionMethod ? $reflection->getName() : '__invoke';
+        $attributes[$method] = array_map(fn (\ReflectionAttribute $ra) => $ra->newInstance(), $reflection_attributes);
+      }
+    }
+    return $attributes;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Hook/HookPriority.php b/core/lib/Drupal/Core/Hook/HookPriority.php
index c712aa4ed634..2fd7c0b0f653 100644
--- a/core/lib/Drupal/Core/Hook/HookPriority.php
+++ b/core/lib/Drupal/Core/Hook/HookPriority.php
@@ -8,7 +8,9 @@
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 
 /**
- * A class to handle updating priority of hook listeners.
+ * Helper class for HookCollectorPass to change the priority of listeners.
+ *
+ * @internal
  */
 class HookPriority {
 
@@ -18,12 +20,17 @@ public function __construct(protected ContainerBuilder $container) {}
    * Change the priority of a hook implementation.
    *
    * @param array $hooks
-   *   The name of the hook.
+   *   The list of hooks to order. The list always contains the hook defined
+   *   in Drupal\Core\Hook\Attribute, and it might also contain
+   *   the hooks listed in the Drupal\Core\Hook\Attribute\HookOrderGroup
+   *   attribute.
    * @param \Drupal\Core\Hook\Attribute\HookOrderBase $attribute
    *   The order attribute.
    * @param array|null $others
    *   Other hook implementations to compare to, if any. The array is a list of
-   *   arrays containing a class, a method, and module.
+   *   lists, each containing class, method,  module.
+   *
+   * @internal
    */
   public function change(array $hooks, HookOrderBase $attribute, ?array $others = NULL): void {
     $class_and_method = "$attribute->class::$attribute->method";
-- 
GitLab


From e247f0400677d5935592cedff45826e2056fec97 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Sat, 14 Dec 2024 10:41:02 -0500
Subject: [PATCH 055/173] Handle hooks implemented on behalf of other modules

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index aeaec41fd46e..2bb0a30d18f4 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -91,9 +91,10 @@ public function process(ContainerBuilder $container): array {
                   self::checkForProceduralOnlyHooks($attribute, $class);
                 }
                 $hook = $attribute->hook;
-                if ($attribute->module) {
-                  $module = $attribute->module;
-                }
+                // This hook may be implemented on behalf of another module.
+                // This prevents one hook implementing on behalf of another
+                // from overriding all following hooks.
+                $moduleForHook = $attribute->module ?: $module;
                 $legacyImplementations[$hook][$module] = '';
                 $implementations[$hook][$module][$class][] = $attribute->method ?: $method;
                 break;
@@ -109,7 +110,7 @@ public function process(ContainerBuilder $container): array {
           }
           if ($hook) {
             foreach ($orderAttributes as $orderAttribute) {
-              $allOrderAttributes[] = $orderAttribute->set(hook: $hook, class: $class, method: $method, module: $module);
+              $allOrderAttributes[] = $orderAttribute->set(hook: $hook, class: $class, method: $method, module: $moduleForHook);
             }
             if ($orderGroup) {
               $orderGroup[] = $hook;
-- 
GitLab


From 23f01066155e914c17ce1f7358749640eef703bf Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Sat, 14 Dec 2024 10:59:52 -0500
Subject: [PATCH 056/173] Stan

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 2bb0a30d18f4..c7b9309d95e0 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -85,16 +85,19 @@ public function process(ContainerBuilder $container): array {
           $orderGroup = FALSE;
           $hook = FALSE;
           foreach ($attributes as $attribute) {
+            // A hook may be implemented on behalf of another module.
+            // This prevents one hook implementing on behalf of another
+            // from overriding all following hooks in this class.
+            $currentModule = $module;
             switch (TRUE) {
               case $attribute instanceof Hook:
                 if ($class !== ProceduralCall::class) {
                   self::checkForProceduralOnlyHooks($attribute, $class);
                 }
                 $hook = $attribute->hook;
-                // This hook may be implemented on behalf of another module.
-                // This prevents one hook implementing on behalf of another
-                // from overriding all following hooks.
-                $moduleForHook = $attribute->module ?: $module;
+                if ($attribute->module) {
+                  $currentModule = $attribute->module;
+                }
                 $legacyImplementations[$hook][$module] = '';
                 $implementations[$hook][$module][$class][] = $attribute->method ?: $method;
                 break;
@@ -110,7 +113,7 @@ public function process(ContainerBuilder $container): array {
           }
           if ($hook) {
             foreach ($orderAttributes as $orderAttribute) {
-              $allOrderAttributes[] = $orderAttribute->set(hook: $hook, class: $class, method: $method, module: $moduleForHook);
+              $allOrderAttributes[] = $orderAttribute->set(hook: $hook, class: $class, method: $method, module: $currentModule);
             }
             if ($orderGroup) {
               $orderGroup[] = $hook;
-- 
GitLab


From d151aaa2ea17d68182026adb2c6ae2141f4ff5c4 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Sat, 14 Dec 2024 11:23:25 -0500
Subject: [PATCH 057/173] Stan

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index c7b9309d95e0..fcf0512a4a0b 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -85,9 +85,9 @@ public function process(ContainerBuilder $container): array {
           $orderGroup = FALSE;
           $hook = FALSE;
           foreach ($attributes as $attribute) {
-            // A hook may be implemented on behalf of another module.
             // This prevents one hook implementing on behalf of another
-            // from overriding all following hooks in this class.
+            // module from overriding any following methods in this
+            // class.
             $currentModule = $module;
             switch (TRUE) {
               case $attribute instanceof Hook:
@@ -113,6 +113,8 @@ public function process(ContainerBuilder $container): array {
           }
           if ($hook) {
             foreach ($orderAttributes as $orderAttribute) {
+              // If $hook is not false then $currentModule is set.
+              /** @phpstan-ignore variable.undefined */
               $allOrderAttributes[] = $orderAttribute->set(hook: $hook, class: $class, method: $method, module: $currentModule);
             }
             if ($orderGroup) {
-- 
GitLab


From ee2b6d1c6eec0a1d03177b3f4c23d2cf78f3b9f7 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Sat, 14 Dec 2024 12:04:38 -0500
Subject: [PATCH 058/173] Refactor attribute loop

---
 .../Core/Hook/Attribute/HookOrderBase.php     | 11 +++++---
 .../Hook/Attribute/HookOrderInterface.php     | 11 +++-----
 .../Drupal/Core/Hook/HookCollectorPass.php    | 27 +++++++++----------
 3 files changed, 22 insertions(+), 27 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
index 4a657bd8497c..d222e979a1fb 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
@@ -33,6 +33,9 @@ class HookOrderBase implements HookOrderInterface {
   /**
    * The module of the hook.
    *
+   * The module of the hook. Note this might be different from the module the
+   * function is in.
+   *
    * @internal
    */
   public string $module;
@@ -48,11 +51,11 @@ public function __construct(public readonly bool $shouldBeLarger) {}
   /**
    * {@inheritdoc}
    */
-  public function set(string $hook, string $class, string $method, string $module): static {
-    $this->hook = $hook;
+  public function set(Hook $hook, string $class): static {
+    $this->hook = $hook->hook;
     $this->class = $class;
-    $this->method = $method;
-    $this->module = $module;
+    $this->method = $hook->method;
+    $this->module = $hook->module;
     return $this;
   }
 
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
index 1c87e3b51a08..3f8ddb76283a 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
@@ -12,16 +12,11 @@ interface HookOrderInterface {
   /**
    * Set the properties on the attributes using this class.
    *
-   * @param string $hook
-   *   The hook to order.
+   * @param Drupal\Core\Hook\Attribute\Hook $hook
+   *   The hook attribute to order.
    * @param string $class
    *   The class the hook is in.
-   * @param string $method
-   *   The method of the hook.
-   * @param string $module
-   *   The module of the hook. Note this might be different from the module the
-   *   class is in.
    */
-  public function set(string $hook, string $class, string $method, string $module): static;
+  public function set(Hook $hook, string $class): static;
 
 }
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index fcf0512a4a0b..f510cef342d0 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -83,23 +83,21 @@ public function process(ContainerBuilder $container): array {
         foreach ($methods as $method => $attributes) {
           $orderAttributes = [];
           $orderGroup = FALSE;
-          $hook = FALSE;
           foreach ($attributes as $attribute) {
-            // This prevents one hook implementing on behalf of another
-            // module from overriding any following methods in this
-            // class.
-            $currentModule = $module;
             switch (TRUE) {
               case $attribute instanceof Hook:
                 if ($class !== ProceduralCall::class) {
                   self::checkForProceduralOnlyHooks($attribute, $class);
                 }
-                $hook = $attribute->hook;
-                if ($attribute->module) {
-                  $currentModule = $attribute->module;
+                $hookAttribute = $attribute;
+                if (!$attribute->module) {
+                  $attribute->module = $module;
                 }
-                $legacyImplementations[$hook][$module] = '';
-                $implementations[$hook][$module][$class][] = $attribute->method ?: $method;
+                if (!$attribute->method) {
+                  $attribute->method = $method;
+                }
+                $legacyImplementations[$attribute->hook][$attribute->module] = '';
+                $implementations[$attribute->hook][$attribute->module][$class][] = $attribute->method;
                 break;
 
               case $attribute instanceof HookOrderInterface:
@@ -111,18 +109,17 @@ public function process(ContainerBuilder $container): array {
                 break;
             }
           }
-          if ($hook) {
+          if (isset($hookAttribute)) {
             foreach ($orderAttributes as $orderAttribute) {
-              // If $hook is not false then $currentModule is set.
-              /** @phpstan-ignore variable.undefined */
-              $allOrderAttributes[] = $orderAttribute->set(hook: $hook, class: $class, method: $method, module: $currentModule);
+              $allOrderAttributes[] = $orderAttribute->set(hook: $hookAttribute, class: $class);
             }
             if ($orderGroup) {
-              $orderGroup[] = $hook;
+              $orderGroup[] = $hookAttribute->hook;
               foreach ($orderGroup as $extraHook) {
                 $orderGroups[$extraHook] = array_merge($orderGroups[$extraHook] ?? [], $orderGroup);
               }
             }
+            unset($hookAttribute);
           }
         }
       }
-- 
GitLab


From 567475162e6c7b4125273e005651ac923de3199d Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Sat, 14 Dec 2024 12:18:55 -0500
Subject: [PATCH 059/173] Handle multiple hook attributes

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index f510cef342d0..afe3fa9a80fe 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -83,19 +83,20 @@ public function process(ContainerBuilder $container): array {
         foreach ($methods as $method => $attributes) {
           $orderAttributes = [];
           $orderGroup = FALSE;
+          $hookAttributes = [];
           foreach ($attributes as $attribute) {
             switch (TRUE) {
               case $attribute instanceof Hook:
                 if ($class !== ProceduralCall::class) {
                   self::checkForProceduralOnlyHooks($attribute, $class);
                 }
-                $hookAttribute = $attribute;
                 if (!$attribute->module) {
                   $attribute->module = $module;
                 }
                 if (!$attribute->method) {
                   $attribute->method = $method;
                 }
+                $hookAttributes[] = $attribute;
                 $legacyImplementations[$attribute->hook][$attribute->module] = '';
                 $implementations[$attribute->hook][$attribute->module][$class][] = $attribute->method;
                 break;
@@ -109,11 +110,16 @@ public function process(ContainerBuilder $container): array {
                 break;
             }
           }
-          if (isset($hookAttribute)) {
+          $hooksCount = count($hookAttributes);
+          if ($hooksCount === 1) {
+            $hookAttribute = reset($hookAttributes);
             foreach ($orderAttributes as $orderAttribute) {
               $allOrderAttributes[] = $orderAttribute->set(hook: $hookAttribute, class: $class);
             }
             if ($orderGroup) {
+              if (!$orderAttributes) {
+                throw new \LogicException('HookOrderGroup requires an order to be specified.');
+              }
               $orderGroup[] = $hookAttribute->hook;
               foreach ($orderGroup as $extraHook) {
                 $orderGroups[$extraHook] = array_merge($orderGroups[$extraHook] ?? [], $orderGroup);
@@ -121,6 +127,9 @@ public function process(ContainerBuilder $container): array {
             }
             unset($hookAttribute);
           }
+          elseif ($hooksCount > 1 && $orderAttributes) {
+            throw new \LogicException('Hook ordering can only be applied to methods with one Hook attribute.');
+          }
         }
       }
     }
-- 
GitLab


From 6474c92e2eb7ea2cedbb30c869d9dbcb391744da Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Sat, 14 Dec 2024 14:53:17 -0500
Subject: [PATCH 060/173] Clean up exceptions

---
 .../Drupal/Core/Hook/Attribute/HookAfter.php  |  5 +-
 .../Drupal/Core/Hook/Attribute/HookBefore.php |  5 +-
 .../Core/Hook/Attribute/HookOrderBase.php     | 11 ----
 .../Core/Hook/Attribute/HookOrderGroup.php    |  2 +-
 .../Hook/Attribute/HookOrderInterface.php     |  2 +-
 .../Drupal/Core/Hook/HookCollectorPass.php    | 60 ++++++++++++++-----
 core/lib/Drupal/Core/Hook/HookPriority.php    | 22 +++----
 7 files changed, 65 insertions(+), 42 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
index 01a68e6f5e59..6e82bd17d848 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
@@ -18,10 +18,11 @@ class HookAfter extends HookOrderBase {
    * Constructs a HookAfter attribute.
    *
    * @param array $modules
-   *   The modules this implementation should run before.
+   *   A list of things this implementation should run after. Each thing is
+   *   either a module name or a list of class and method.
    */
   public function __construct(
-    public array $modules,
+    public readonly array $modules,
   ) {
     parent::__construct(FALSE);
   }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
index ee87e431418c..bde7335ea576 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
@@ -18,10 +18,11 @@ class HookBefore extends HookOrderBase {
    * Constructs a HookBefore lib/Drupal/Core/Hook/Attribute/attribute.
    *
    * @param array $modules
-   *   The modules this implementation should run before.
+   *   A list of things this implementation should run before. Each thing is
+   *   either a module name or a list of class and method.
    */
   public function __construct(
-    public array $modules,
+    public readonly array $modules,
   ) {
     parent::__construct(TRUE);
   }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
index d222e979a1fb..f7297b3486e3 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
@@ -30,16 +30,6 @@ class HookOrderBase implements HookOrderInterface {
    */
   public string $method;
 
-  /**
-   * The module of the hook.
-   *
-   * The module of the hook. Note this might be different from the module the
-   * function is in.
-   *
-   * @internal
-   */
-  public string $module;
-
   /**
    * Constructs a HookOrderBase class.
    *
@@ -55,7 +45,6 @@ public function set(Hook $hook, string $class): static {
     $this->hook = $hook->hook;
     $this->class = $class;
     $this->method = $hook->method;
-    $this->module = $hook->module;
     return $this;
   }
 
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php
index 0e0b43044af6..c4f45c6728b1 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php
@@ -38,7 +38,7 @@ class HookOrderGroup {
    *   A list of hooks to sort together.
    */
   public function __construct(
-    public array $group,
+    public readonly array $group,
   ) {}
 
 }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
index 3f8ddb76283a..edd5be7186e9 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
@@ -12,7 +12,7 @@ interface HookOrderInterface {
   /**
    * Set the properties on the attributes using this class.
    *
-   * @param Drupal\Core\Hook\Attribute\Hook $hook
+   * @param \Drupal\Core\Hook\Attribute\Hook $hook
    *   The hook attribute to order.
    * @param string $class
    *   The class the hook is in.
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index afe3fa9a80fe..ead37f80d1a1 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -15,6 +15,7 @@
 use Drupal\Core\Hook\Attribute\StopProceduralHookScan;
 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
 
 /**
  * Collects and registers hook implementations.
@@ -110,24 +111,30 @@ public function process(ContainerBuilder $container): array {
                 break;
             }
           }
-          $hooksCount = count($hookAttributes);
+          // If no ordering is required the processing of this method is done.
+          if (!$orderAttributes) {
+            if ($orderGroup) {
+              throw new \LogicException('HookOrderGroup requires an order to be specified.');
+            }
+            continue;
+          }
+          // Now process ordering, if possible.
+          if (!$hooksCount = count($hookAttributes)) {
+            throw new \LogicException('Order attributes require a Hook attribute.');
+          }
           if ($hooksCount === 1) {
             $hookAttribute = reset($hookAttributes);
             foreach ($orderAttributes as $orderAttribute) {
               $allOrderAttributes[] = $orderAttribute->set(hook: $hookAttribute, class: $class);
             }
             if ($orderGroup) {
-              if (!$orderAttributes) {
-                throw new \LogicException('HookOrderGroup requires an order to be specified.');
-              }
               $orderGroup[] = $hookAttribute->hook;
               foreach ($orderGroup as $extraHook) {
                 $orderGroups[$extraHook] = array_merge($orderGroups[$extraHook] ?? [], $orderGroup);
               }
             }
-            unset($hookAttribute);
           }
-          elseif ($hooksCount > 1 && $orderAttributes) {
+          else {
             throw new \LogicException('Hook ordering can only be applied to methods with one Hook attribute.');
           }
         }
@@ -198,11 +205,7 @@ protected static function registerServices(ContainerBuilder $container, HookColl
           }
           foreach ($method_hooks as $method) {
             $map[$hook][$class][$method] = $module;
-            $definition->addTag('kernel.event_listener', [
-              'event' => "drupal_hook.$hook",
-              'method' => $method,
-              'priority' => $priority--,
-            ]);
+            $priority = self::addTagToDefinition($definition, "drupal_hook.$hook", $method, $priority);
           }
         }
         unset($implementations[$hook][$module]);
@@ -243,9 +246,14 @@ protected static function reOrderServices(ContainerBuilder $container, array $al
         $others = [];
         foreach ($orderAttribute->modules as $module) {
           foreach ($hooks as $hook) {
-            foreach ($implementations[$hook][$module] ?? [] as $class => $methods) {
-              foreach ($methods as $method) {
-                $others[] = [$class, $method, $module];
+            if (is_array($module)) {
+              $others[] = $module;
+            }
+            else {
+              foreach ($implementations[$hook][$module] ?? [] as $class => $methods) {
+                foreach ($methods as $method) {
+                  $others[] = [$class, $method];
+                }
               }
             }
           }
@@ -495,4 +503,28 @@ protected static function getAttributeInstances(array $attributes, array $reflec
     return $attributes;
   }
 
+  /**
+   * Adds an event listener tag to a service definition.
+   *
+   * @param \Symfony\Component\DependencyInjection\Definition $definition
+   *   The service definition.
+   * @param string $event
+   *   The name of the event, typically starts with drupal_hook.
+   * @param string $method
+   *   The method.
+   * @param int $priority
+   *   The priority.
+   *
+   * @return int
+   *   A new priority, guaranteed to be lower than $priority.
+   */
+  public static function addTagToDefinition(Definition $definition, string $event, string $method, int $priority): int {
+    $definition->addTag('kernel.event_listener', [
+      'event' => $event,
+      'method' => $method,
+      'priority' => $priority--,
+    ]);
+    return $priority;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Hook/HookPriority.php b/core/lib/Drupal/Core/Hook/HookPriority.php
index 2fd7c0b0f653..59bb9a89243e 100644
--- a/core/lib/Drupal/Core/Hook/HookPriority.php
+++ b/core/lib/Drupal/Core/Hook/HookPriority.php
@@ -28,7 +28,7 @@ public function __construct(protected ContainerBuilder $container) {}
    *   The order attribute.
    * @param array|null $others
    *   Other hook implementations to compare to, if any. The array is a list of
-   *   lists, each containing class, method,  module.
+   *   class and method pairs.
    *
    * @internal
    */
@@ -44,16 +44,16 @@ public function change(array $hooks, HookOrderBase $attribute, ?array $others =
       $combinedHookTag = implode(':', $hooks);
       $event = "drupal_hook.$combinedHookTag";
       $data = $others;
-      $data[] = [$attribute->class, $attribute->method, $attribute->module];
+      $data[] = [$attribute->class, $attribute->method];
       $priority = 0;
-      foreach ($data as [$class, $method, $module]) {
-        $definition = $this->container->findDefinition($class);
-        $definition->addTag('kernel.event_listener', [
-          'event' => "drupal_hook.$combinedHookTag",
-          'method' => $method,
-          'priority' => $priority--,
-        ]);
-        $map[$combinedHookTag][$class][$method] = $module;
+      foreach ($data as [$class, $method]) {
+        foreach ($hooks as $hook) {
+          if (isset($map[$hook][$class][$method])) {
+            $map[$combinedHookTag][$class][$method] = $map[$hook][$class][$method];
+            $priority = HookCollectorPass::addTagToDefinition($this->container->findDefinition($class), $event, $method, $priority);
+            break;
+          }
+        }
       }
       $this->container->setParameter('hook_implementations_map', $map);
     }
@@ -136,7 +136,7 @@ public function change(array $hooks, HookOrderBase $attribute, ?array $others =
    * @return void
    */
   public function set(string $class, int $key, int $priority): void {
-    $definition = $this->container->getDefinition($class);
+    $definition = $this->container->findDefinition($class);
     $tags = $definition->getTags();
     $tags['kernel.event_listener'][$key]['priority'] = $priority;
     $definition->setTags($tags);
-- 
GitLab


From 21b663dbb25fc0b2b19f938dc2bc020212e39bb6 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Sat, 14 Dec 2024 16:31:00 -0500
Subject: [PATCH 061/173] Reorganize test modules

---
 .../hook_order_first_alphabetically.info.yml} |  1 +
 .../src/Hook/TestHookAfter.php                | 33 +++++++++++++++++
 .../src/Hook/TestHookBefore.php               | 35 ++++++++++++++++++
 .../src/Hook/TestHookFirst.php                | 35 ++++++++++++++++++
 .../src/Hook/TestHookLast.php}                | 18 ++++-----
 .../src/Hook/TestHookOrderGroup.php}          | 17 ++++-----
 .../src/Hook/FirstAlphabeticallyHooks1.php    | 37 -------------------
 .../hook_order_first_alphabetically2.info.yml |  6 ---
 .../src/Hook/FirstAlphabeticallyHooks2.php    | 35 ------------------
 .../hook_order_first_alphabetically3.info.yml |  6 ---
 .../src/Hook/FirstAlphabeticallyHooks3.php    | 37 -------------------
 .../hook_order_first_alphabetically4.info.yml |  6 ---
 .../hook_order_first_alphabetically5.info.yml |  6 ---
 .../hook_order_last_alphabetically.info.yml}  |  1 +
 .../src/Hook/TestHookAfter.php                | 35 ++++++++++++++++++
 .../src/Hook/TestHookBefore.php}              | 18 ++++-----
 .../src/Hook/TestHookFirst.php}               | 18 ++++-----
 .../src/Hook/TestHookLast.php                 | 35 ++++++++++++++++++
 .../src/Hook/TestHookOrderGroup.php}          | 15 +++-----
 .../hook_order_last_alphabetically2.info.yml  |  6 ---
 .../src/Hook/LastAlphabeticallyHooks2.php     | 37 -------------------
 .../hook_order_last_alphabetically3.info.yml  |  6 ---
 .../hook_order_last_alphabetically4.info.yml  |  6 ---
 .../hook_order_last_alphabetically5.info.yml  |  6 ---
 .../src/Hook/LastAlphabeticallyHooks5.php     | 37 -------------------
 25 files changed, 212 insertions(+), 280 deletions(-)
 rename core/modules/system/tests/modules/{hook_order_first_alphabetically1/hook_order_first_alphabetically1.info.yml => hook_order_first_alphabetically/hook_order_first_alphabetically.info.yml} (82%)
 create mode 100644 core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php
 create mode 100644 core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookBefore.php
 create mode 100644 core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookFirst.php
 rename core/modules/system/tests/modules/{hook_order_first_alphabetically5/src/Hook/FirstAlphabeticallyHooks5.php => hook_order_first_alphabetically/src/Hook/TestHookLast.php} (52%)
 rename core/modules/system/tests/modules/{hook_order_first_alphabetically4/src/Hook/FirstAlphabeticallyHooks4.php => hook_order_first_alphabetically/src/Hook/TestHookOrderGroup.php} (66%)
 delete mode 100644 core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php
 delete mode 100644 core/modules/system/tests/modules/hook_order_first_alphabetically2/hook_order_first_alphabetically2.info.yml
 delete mode 100644 core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php
 delete mode 100644 core/modules/system/tests/modules/hook_order_first_alphabetically3/hook_order_first_alphabetically3.info.yml
 delete mode 100644 core/modules/system/tests/modules/hook_order_first_alphabetically3/src/Hook/FirstAlphabeticallyHooks3.php
 delete mode 100644 core/modules/system/tests/modules/hook_order_first_alphabetically4/hook_order_first_alphabetically4.info.yml
 delete mode 100644 core/modules/system/tests/modules/hook_order_first_alphabetically5/hook_order_first_alphabetically5.info.yml
 rename core/modules/system/tests/modules/{hook_order_last_alphabetically1/hook_order_last_alphabetically1.info.yml => hook_order_last_alphabetically/hook_order_last_alphabetically.info.yml} (82%)
 create mode 100644 core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookAfter.php
 rename core/modules/system/tests/modules/{hook_order_last_alphabetically3/src/Hook/LastAlphabeticallyHooks3.php => hook_order_last_alphabetically/src/Hook/TestHookBefore.php} (53%)
 rename core/modules/system/tests/modules/{hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php => hook_order_last_alphabetically/src/Hook/TestHookFirst.php} (52%)
 create mode 100644 core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookLast.php
 rename core/modules/system/tests/modules/{hook_order_last_alphabetically4/src/Hook/LastAlphabeticallyHooks4.php => hook_order_last_alphabetically/src/Hook/TestHookOrderGroup.php} (55%)
 delete mode 100644 core/modules/system/tests/modules/hook_order_last_alphabetically2/hook_order_last_alphabetically2.info.yml
 delete mode 100644 core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php
 delete mode 100644 core/modules/system/tests/modules/hook_order_last_alphabetically3/hook_order_last_alphabetically3.info.yml
 delete mode 100644 core/modules/system/tests/modules/hook_order_last_alphabetically4/hook_order_last_alphabetically4.info.yml
 delete mode 100644 core/modules/system/tests/modules/hook_order_last_alphabetically5/hook_order_last_alphabetically5.info.yml
 delete mode 100644 core/modules/system/tests/modules/hook_order_last_alphabetically5/src/Hook/LastAlphabeticallyHooks5.php

diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically1/hook_order_first_alphabetically1.info.yml b/core/modules/system/tests/modules/hook_order_first_alphabetically/hook_order_first_alphabetically.info.yml
similarity index 82%
rename from core/modules/system/tests/modules/hook_order_first_alphabetically1/hook_order_first_alphabetically1.info.yml
rename to core/modules/system/tests/modules/hook_order_first_alphabetically/hook_order_first_alphabetically.info.yml
index d61a2ddc1212..bd8dc0b2f73d 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically1/hook_order_first_alphabetically1.info.yml
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/hook_order_first_alphabetically.info.yml
@@ -3,4 +3,5 @@ type: module
 description: 'Test module used to test hook ordering.'
 package: Testing
 version: VERSION
+core_version_requirement: '*'
 hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php
new file mode 100644
index 000000000000..49748e2eb9fa
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_first_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Attribute\HookAfter;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering attribute.
+ */
+class TestHookAfter {
+
+  /**
+   * This pair tests #[HookAfter].
+   */
+  #[HookAfter(['hook_order_last_alphabetically'])]
+  #[Hook('custom_hook_test_hook_after')]
+  public static function hookAfter(): void {
+    $GLOBALS['HookAfter'] = 'HookAfter';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookBefore.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookBefore.php
new file mode 100644
index 000000000000..6ac7468053d6
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookBefore.php
@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_first_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering attribute.
+ */
+class TestHookBefore {
+
+  /**
+   * This pair tests #[HookBefore].
+   */
+  #[Hook('custom_hook_test_hook_before')]
+  public static function hookBefore(): void {
+    // This should be run after so HookBefore should not be set.
+    if (!isset($GLOBALS['HookBefore'])) {
+      $GLOBALS['HookOutOfOrderTestingHookBefore'] = 'HookOutOfOrderTestingHookBefore';
+    }
+    $GLOBALS['HookRanTestingHookBefore'] = 'HookRanTestingHookBefore';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookFirst.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookFirst.php
new file mode 100644
index 000000000000..012e3f0d2c54
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookFirst.php
@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_first_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering attribute.
+ */
+class TestHookFirst {
+
+  /**
+   * This pair tests #[HookFirst].
+   */
+  #[Hook('custom_hook_test_hook_first')]
+  public static function hookFirst(): void {
+    // This should be run after so HookFirst should not be set.
+    if (!isset($GLOBALS['HookFirst'])) {
+      $GLOBALS['HookOutOfOrderTestingHookFirst'] = 'HookOutOfOrderTestingHookFirst';
+    }
+    $GLOBALS['HookRanTestingHookFirst'] = 'HookRanTestingHookFirst';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically5/src/Hook/FirstAlphabeticallyHooks5.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php
similarity index 52%
rename from core/modules/system/tests/modules/hook_order_first_alphabetically5/src/Hook/FirstAlphabeticallyHooks5.php
rename to core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php
index 540cd86c604c..0a82ba6e55b3 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically5/src/Hook/FirstAlphabeticallyHooks5.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php
@@ -2,7 +2,7 @@
 
 declare(strict_types=1);
 
-namespace Drupal\hook_order_first_alphabetically5\Hook;
+namespace Drupal\hook_order_first_alphabetically\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\Core\Hook\Attribute\HookLast;
@@ -11,24 +11,22 @@
  * Hook implementations for verifying ordering hooks by attributes.
  *
  * We must ensure that the order of the modules is expected and then change
- * the order that the hooks are run in order to verify. All of these modules
- * come in a pair first alphabetically and last alphabetically.
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
  *
  * In the normal order a hook implemented by first alphabetically would run
  * before the same hook in last alphabetically.
  *
- * Each pair tests one hook order attribute.
- *
- * This pair tests #[HookLast].
+ * Each method pair tests one hook ordering attribute.
  */
-class FirstAlphabeticallyHooks5 {
+class TestHookLast {
 
   /**
-   * After LastAlphabeticallyHooks5::cacheFlush.
+   * This pair tests #[HookLast].
    */
   #[HookLast]
-  #[Hook('cache_flush')]
-  public static function cacheFlush(): void {
+  #[Hook('custom_hook_test_hook_last')]
+  public static function hookLast(): void {
     $GLOBALS['HookLast'] = 'HookLast';
   }
 
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically4/src/Hook/FirstAlphabeticallyHooks4.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderGroup.php
similarity index 66%
rename from core/modules/system/tests/modules/hook_order_first_alphabetically4/src/Hook/FirstAlphabeticallyHooks4.php
rename to core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderGroup.php
index 08eef7984c09..be8b4274cc26 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically4/src/Hook/FirstAlphabeticallyHooks4.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderGroup.php
@@ -2,7 +2,7 @@
 
 declare(strict_types=1);
 
-namespace Drupal\hook_order_first_alphabetically4\Hook;
+namespace Drupal\hook_order_first_alphabetically\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\Core\Hook\Attribute\HookAfter;
@@ -12,23 +12,20 @@
  * Hook implementations for verifying ordering hooks by attributes.
  *
  * We must ensure that the order of the modules is expected and then change
- * the order that the hooks are run in order to verify. All of these modules
- * come in a pair first alphabetically and last alphabetically.
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
  *
  * In the normal order a hook implemented by first alphabetically would run
  * before the same hook in last alphabetically.
  *
- * Each pair tests one hook order attribute.
- *
- * This pair tests #[HookOrderGroup].
- * This attribute must be paired with #[HookAfter] or #[HookBefore].
+ * Each method pair tests one hook ordering attribute.
  */
-class FirstAlphabeticallyHooks4 {
+class TestHookOrderGroup {
 
   /**
-   * After LastAlphabeticallyHooks4::customHookExtraTypes.
+   * This pair tests #[HookOrderGroup].
    */
-  #[HookAfter(['hook_order_last_alphabetically4'])]
+  #[HookAfter(['hook_order_last_alphabetically'])]
   #[HookOrderGroup(['custom_hook_extra_types2_alter'])]
   #[Hook('custom_hook_extra_types1_alter')]
   public static function customHookExtraTypes(): void {
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php b/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php
deleted file mode 100644
index f197cea8d043..000000000000
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically1/src/Hook/FirstAlphabeticallyHooks1.php
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\hook_order_first_alphabetically1\Hook;
-
-use Drupal\Core\Hook\Attribute\Hook;
-
-/**
- * Hook implementations for verifying ordering hooks by attributes.
- *
- * We must ensure that the order of the modules is expected and then change
- * the order that the hooks are run in order to verify. All of these modules
- * come in a pair first alphabetically and last alphabetically.
- *
- * In the normal order a hook implemented by first alphabetically would run
- * before the same hook in last alphabetically.
- *
- * Each pair tests one hook order attribute.
- *
- * This pair tests #[HookFirst].
- */
-class FirstAlphabeticallyHooks1 {
-
-  /**
-   * After LastAlphabeticallyHooks1::cacheFlush.
-   */
-  #[Hook('cache_flush')]
-  public static function cacheFlush(): void {
-    // This should be run after so HookFirst should not be set.
-    if (!isset($GLOBALS['HookFirst'])) {
-      $GLOBALS['HookOutOfOrderTestingFirst'] = 'HookOutOfOrderTestingFirst';
-    }
-    $GLOBALS['HookRanTestingFirst'] = 'HookRanTestingFirst';
-  }
-
-}
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically2/hook_order_first_alphabetically2.info.yml b/core/modules/system/tests/modules/hook_order_first_alphabetically2/hook_order_first_alphabetically2.info.yml
deleted file mode 100644
index d61a2ddc1212..000000000000
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically2/hook_order_first_alphabetically2.info.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-name: first alphabetically
-type: module
-description: 'Test module used to test hook ordering.'
-package: Testing
-version: VERSION
-hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php b/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php
deleted file mode 100644
index 341688ce600d..000000000000
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically2/src/Hook/FirstAlphabeticallyHooks2.php
+++ /dev/null
@@ -1,35 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\hook_order_first_alphabetically2\Hook;
-
-use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\HookAfter;
-
-/**
- * Hook implementations for verifying ordering hooks by attributes.
- *
- * We must ensure that the order of the modules is expected and then change
- * the order that the hooks are run in order to verify. All of these modules
- * come in a pair first alphabetically and last alphabetically.
- *
- * In the normal order a hook implemented by first alphabetically would run
- * before the same hook in last alphabetically.
- *
- * Each pair tests one hook order attribute.
- *
- * This pair tests #[HookAfter].
- */
-class FirstAlphabeticallyHooks2 {
-
-  /**
-   * After LastAlphabeticallyHooks2::cacheFlush.
-   */
-  #[HookAfter(['hook_order_last_alphabetically2'])]
-  #[Hook('cache_flush')]
-  public static function cacheFlush(): void {
-    $GLOBALS['HookAfter'] = 'HookAfter';
-  }
-
-}
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically3/hook_order_first_alphabetically3.info.yml b/core/modules/system/tests/modules/hook_order_first_alphabetically3/hook_order_first_alphabetically3.info.yml
deleted file mode 100644
index d61a2ddc1212..000000000000
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically3/hook_order_first_alphabetically3.info.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-name: first alphabetically
-type: module
-description: 'Test module used to test hook ordering.'
-package: Testing
-version: VERSION
-hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically3/src/Hook/FirstAlphabeticallyHooks3.php b/core/modules/system/tests/modules/hook_order_first_alphabetically3/src/Hook/FirstAlphabeticallyHooks3.php
deleted file mode 100644
index 479c7d85d149..000000000000
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically3/src/Hook/FirstAlphabeticallyHooks3.php
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\hook_order_first_alphabetically3\Hook;
-
-use Drupal\Core\Hook\Attribute\Hook;
-
-/**
- * Hook implementations for verifying ordering hooks by attributes.
- *
- * We must ensure that the order of the modules is expected and then change
- * the order that the hooks are run in order to verify. All of these modules
- * come in a pair first alphabetically and last alphabetically.
- *
- * In the normal order a hook implemented by first alphabetically would run
- * before the same hook in last alphabetically.
- *
- * Each pair tests one hook order attribute.
- *
- * This pair tests #[HookBefore].
- */
-class FirstAlphabeticallyHooks3 {
-
-  /**
-   * After LastAlphabeticallyHooks3::cacheFlush.
-   */
-  #[Hook('cache_flush')]
-  public static function cacheFlush(): void {
-    // This should be run after so HookBefore should not be set.
-    if (!isset($GLOBALS['HookBefore'])) {
-      $GLOBALS['HookOutOfOrderTestingBefore'] = 'HookOutOfOrderTestingBefore';
-    }
-    $GLOBALS['HookRanTestingBefore'] = 'HookRanTestingBefore';
-  }
-
-}
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically4/hook_order_first_alphabetically4.info.yml b/core/modules/system/tests/modules/hook_order_first_alphabetically4/hook_order_first_alphabetically4.info.yml
deleted file mode 100644
index d61a2ddc1212..000000000000
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically4/hook_order_first_alphabetically4.info.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-name: first alphabetically
-type: module
-description: 'Test module used to test hook ordering.'
-package: Testing
-version: VERSION
-hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically5/hook_order_first_alphabetically5.info.yml b/core/modules/system/tests/modules/hook_order_first_alphabetically5/hook_order_first_alphabetically5.info.yml
deleted file mode 100644
index d61a2ddc1212..000000000000
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically5/hook_order_first_alphabetically5.info.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-name: first alphabetically
-type: module
-description: 'Test module used to test hook ordering.'
-package: Testing
-version: VERSION
-hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically1/hook_order_last_alphabetically1.info.yml b/core/modules/system/tests/modules/hook_order_last_alphabetically/hook_order_last_alphabetically.info.yml
similarity index 82%
rename from core/modules/system/tests/modules/hook_order_last_alphabetically1/hook_order_last_alphabetically1.info.yml
rename to core/modules/system/tests/modules/hook_order_last_alphabetically/hook_order_last_alphabetically.info.yml
index 59334b38d2fc..aa08f259dc2e 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically1/hook_order_last_alphabetically1.info.yml
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/hook_order_last_alphabetically.info.yml
@@ -3,4 +3,5 @@ type: module
 description: 'Test module used to test hook ordering.'
 package: Testing
 version: VERSION
+core_version_requirement: '*'
 hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookAfter.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookAfter.php
new file mode 100644
index 000000000000..c01e15b71f61
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookAfter.php
@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_last_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering attribute.
+ */
+class TestHookAfter {
+
+  /**
+   * This pair tests #[HookAfter].
+   */
+  #[Hook('custom_hook_test_hook_after')]
+  public static function hookAfter(): void {
+    // This should be run before so HookAfter should not be set.
+    if (isset($GLOBALS['HookAfter'])) {
+      $GLOBALS['HookOutOfOrderTestingHookAfter'] = 'HookOutOfOrderTestingHookAfter';
+    }
+    $GLOBALS['HookRanTestingHookAfter'] = 'HookRanTestingHookAfter';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically3/src/Hook/LastAlphabeticallyHooks3.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookBefore.php
similarity index 53%
rename from core/modules/system/tests/modules/hook_order_last_alphabetically3/src/Hook/LastAlphabeticallyHooks3.php
rename to core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookBefore.php
index a25ac5454247..946377092261 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically3/src/Hook/LastAlphabeticallyHooks3.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookBefore.php
@@ -2,7 +2,7 @@
 
 declare(strict_types=1);
 
-namespace Drupal\hook_order_last_alphabetically3\Hook;
+namespace Drupal\hook_order_last_alphabetically\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\Core\Hook\Attribute\HookBefore;
@@ -11,23 +11,21 @@
  * Hook implementations for verifying ordering hooks by attributes.
  *
  * We must ensure that the order of the modules is expected and then change
- * the order that the hooks are run in order to verify. All of these modules
- * come in a pair first alphabetically and last alphabetically.
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
  *
  * In the normal order a hook implemented by first alphabetically would run
  * before the same hook in last alphabetically.
  *
- * Each pair tests one hook order attribute.
- *
- * This pair tests #[HookBefore].
+ * Each method pair tests one hook ordering attribute.
  */
-class LastAlphabeticallyHooks3 {
+class TestHookBefore {
 
   /**
-   * Before FirstAlphabeticallyHooks3::cacheFlush.
+   * This pair tests #[HookBefore].
    */
-  #[HookBefore(['hook_order_first_alphabetically3'])]
-  #[Hook('cache_flush')]
+  #[HookBefore(['hook_order_first_alphabetically'])]
+  #[Hook('custom_hook_test_hook_before')]
   public static function cacheFlush(): void {
     $GLOBALS['HookBefore'] = 'HookBefore';
   }
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookFirst.php
similarity index 52%
rename from core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php
rename to core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookFirst.php
index 059e95d0f4c1..3980b23bfb85 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically1/src/Hook/LastAlphabeticallyHooks1.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookFirst.php
@@ -2,7 +2,7 @@
 
 declare(strict_types=1);
 
-namespace Drupal\hook_order_last_alphabetically1\Hook;
+namespace Drupal\hook_order_last_alphabetically\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\Core\Hook\Attribute\HookFirst;
@@ -11,24 +11,22 @@
  * Hook implementations for verifying ordering hooks by attributes.
  *
  * We must ensure that the order of the modules is expected and then change
- * the order that the hooks are run in order to verify. All of these modules
- * come in a pair first alphabetically and last alphabetically.
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
  *
  * In the normal order a hook implemented by first alphabetically would run
  * before the same hook in last alphabetically.
  *
- * Each pair tests one hook order attribute.
- *
- * This pair tests #[HookFirst].
+ * Each method pair tests one hook ordering attribute.
  */
-class LastAlphabeticallyHooks1 {
+class TestHookFirst {
 
   /**
-   * Before FirstAlphabeticallyHooks1::cacheFlush.
+   * This pair tests #[HookFirst].
    */
   #[HookFirst]
-  #[Hook('cache_flush')]
-  public static function cacheFlush(): void {
+  #[Hook('custom_hook_test_hook_first')]
+  public static function hookFirst(): void {
     $GLOBALS['HookFirst'] = 'HookFirst';
   }
 
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookLast.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookLast.php
new file mode 100644
index 000000000000..864016a538b1
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookLast.php
@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_last_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering attribute.
+ */
+class TestHookLast {
+
+  /**
+   * This pair tests #[HookLast].
+   */
+  #[Hook('custom_hook_test_hook_last')]
+  public static function hookLast(): void {
+    // This should be run before so HookLast should not be set.
+    if (isset($GLOBALS['HookLast'])) {
+      $GLOBALS['HookOutOfOrderTestingHookLast'] = 'HookOutOfOrderTestingHookLast';
+    }
+    $GLOBALS['HookRanTestingHookLast'] = 'HookRanTestingHookLast';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically4/src/Hook/LastAlphabeticallyHooks4.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderGroup.php
similarity index 55%
rename from core/modules/system/tests/modules/hook_order_last_alphabetically4/src/Hook/LastAlphabeticallyHooks4.php
rename to core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderGroup.php
index cc7244a9eeb6..607a9d376290 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically4/src/Hook/LastAlphabeticallyHooks4.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderGroup.php
@@ -2,7 +2,7 @@
 
 declare(strict_types=1);
 
-namespace Drupal\hook_order_last_alphabetically4\Hook;
+namespace Drupal\hook_order_last_alphabetically\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
 
@@ -10,21 +10,18 @@
  * Hook implementations for verifying ordering hooks by attributes.
  *
  * We must ensure that the order of the modules is expected and then change
- * the order that the hooks are run in order to verify. All of these modules
- * come in a pair first alphabetically and last alphabetically.
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
  *
  * In the normal order a hook implemented by first alphabetically would run
  * before the same hook in last alphabetically.
  *
- * Each pair tests one hook order attribute.
- *
- * This pair tests #[HookOrderGroup].
- * This attribute must be paired with #[HookAfter] or #[HookBefore].
+ * Each method pair tests one hook ordering attribute.
  */
-class LastAlphabeticallyHooks4 {
+class TestHookOrderGroup {
 
   /**
-   * Before FirstAlphabeticallyHooks4::customHookExtraTypes.
+   * This pair tests #[HookOrderGroup].
    */
   #[Hook('custom_hook_extra_types2_alter')]
   public static function customHookExtraTypes(): void {
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically2/hook_order_last_alphabetically2.info.yml b/core/modules/system/tests/modules/hook_order_last_alphabetically2/hook_order_last_alphabetically2.info.yml
deleted file mode 100644
index 59334b38d2fc..000000000000
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically2/hook_order_last_alphabetically2.info.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-name: Hook ordering last
-type: module
-description: 'Test module used to test hook ordering.'
-package: Testing
-version: VERSION
-hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php b/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php
deleted file mode 100644
index 02df5b541c46..000000000000
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically2/src/Hook/LastAlphabeticallyHooks2.php
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\hook_order_last_alphabetically2\Hook;
-
-use Drupal\Core\Hook\Attribute\Hook;
-
-/**
- * Hook implementations for verifying ordering hooks by attributes.
- *
- * We must ensure that the order of the modules is expected and then change
- * the order that the hooks are run in order to verify. All of these modules
- * come in a pair first alphabetically and last alphabetically.
- *
- * In the normal order a hook implemented by first alphabetically would run
- * before the same hook in last alphabetically.
- *
- * Each pair tests one hook order attribute.
- *
- * This pair tests #[HookAfter].
- */
-class LastAlphabeticallyHooks2 {
-
-  /**
-   * Before FirstAlphabeticallyHooks2::cacheFlush.
-   */
-  #[Hook('cache_flush')]
-  public static function cacheFlush(): void {
-    // This should be run before so HookAfter should not be set.
-    if (isset($GLOBALS['HookAfter'])) {
-      $GLOBALS['HookOutOfOrderTestingAfter'] = 'HookOutOfOrderTestingAfter';
-    }
-    $GLOBALS['HookRanTestingAfter'] = 'HookRanTestingAfter';
-  }
-
-}
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically3/hook_order_last_alphabetically3.info.yml b/core/modules/system/tests/modules/hook_order_last_alphabetically3/hook_order_last_alphabetically3.info.yml
deleted file mode 100644
index 59334b38d2fc..000000000000
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically3/hook_order_last_alphabetically3.info.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-name: Hook ordering last
-type: module
-description: 'Test module used to test hook ordering.'
-package: Testing
-version: VERSION
-hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically4/hook_order_last_alphabetically4.info.yml b/core/modules/system/tests/modules/hook_order_last_alphabetically4/hook_order_last_alphabetically4.info.yml
deleted file mode 100644
index 59334b38d2fc..000000000000
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically4/hook_order_last_alphabetically4.info.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-name: Hook ordering last
-type: module
-description: 'Test module used to test hook ordering.'
-package: Testing
-version: VERSION
-hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically5/hook_order_last_alphabetically5.info.yml b/core/modules/system/tests/modules/hook_order_last_alphabetically5/hook_order_last_alphabetically5.info.yml
deleted file mode 100644
index 59334b38d2fc..000000000000
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically5/hook_order_last_alphabetically5.info.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-name: Hook ordering last
-type: module
-description: 'Test module used to test hook ordering.'
-package: Testing
-version: VERSION
-hidden: true
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically5/src/Hook/LastAlphabeticallyHooks5.php b/core/modules/system/tests/modules/hook_order_last_alphabetically5/src/Hook/LastAlphabeticallyHooks5.php
deleted file mode 100644
index 75a0dbe4c785..000000000000
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically5/src/Hook/LastAlphabeticallyHooks5.php
+++ /dev/null
@@ -1,37 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\hook_order_last_alphabetically5\Hook;
-
-use Drupal\Core\Hook\Attribute\Hook;
-
-/**
- * Hook implementations for verifying ordering hooks by attributes.
- *
- * We must ensure that the order of the modules is expected and then change
- * the order that the hooks are run in order to verify. All of these modules
- * come in a pair first alphabetically and last alphabetically.
- *
- * In the normal order a hook implemented by first alphabetically would run
- * before the same hook in last alphabetically.
- *
- * Each pair tests one hook order attribute.
- *
- * This pair tests #[HookLast].
- */
-class LastAlphabeticallyHooks5 {
-
-  /**
-   * Before FirstAlphabeticallyHooks5::cacheFlush.
-   */
-  #[Hook('cache_flush')]
-  public static function cacheFlush(): void {
-    // This should be run before so HookLast should not be set.
-    if (isset($GLOBALS['HookLast'])) {
-      $GLOBALS['HookOutOfOrderTestingLast'] = 'HookOutOfOrderTestingLast';
-    }
-    $GLOBALS['HookRanTestingLast'] = 'HookRanTestingLast';
-  }
-
-}
-- 
GitLab


From 7f780de9ffddbbe955b32a7328fe47f625cfea8c Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Sat, 14 Dec 2024 16:31:52 -0500
Subject: [PATCH 062/173] Update test to use new modules

---
 .../Core/Hook/HookCollectorPassTest.php       | 68 +++++++++++--------
 1 file changed, 38 insertions(+), 30 deletions(-)

diff --git a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
index 9412573232ca..9db34afb478f 100644
--- a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
@@ -141,15 +141,17 @@ public function testHookAttribute(): void {
    */
   public function testHookFirst(): void {
     $module_installer = $this->container->get('module_installer');
-    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically1']));
-    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically1']));
+    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically']));
+    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically']));
     $this->assertFalse(isset($GLOBALS['HookFirst']));
-    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingFirst']));
-    $this->assertFalse(isset($GLOBALS['HookRanTestingFirst']));
-    drupal_flush_all_caches();
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingHookFirst']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingHookFirst']));
+    $module_handler = $this->container->get('module_handler');
+    $data = ['hi'];
+    $module_handler->invokeAll('custom_hook_test_hook_first', $data);
     $this->assertTrue(isset($GLOBALS['HookFirst']));
-    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingFirst']));
-    $this->assertTrue(isset($GLOBALS['HookRanTestingFirst']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingHookFirst']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingHookFirst']));
   }
 
   /**
@@ -157,15 +159,17 @@ public function testHookFirst(): void {
    */
   public function testHookAfter(): void {
     $module_installer = $this->container->get('module_installer');
-    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically2']));
-    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically2']));
+    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically']));
+    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically']));
     $this->assertFalse(isset($GLOBALS['HookAfter']));
-    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingAfter']));
-    $this->assertFalse(isset($GLOBALS['HookRanTestingAfter']));
-    drupal_flush_all_caches();
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingHookAfter']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingHookAfter']));
+    $module_handler = $this->container->get('module_handler');
+    $data = ['hi'];
+    $module_handler->invokeAll('custom_hook_test_hook_after', $data);
     $this->assertTrue(isset($GLOBALS['HookAfter']));
-    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingAfter']));
-    $this->assertTrue(isset($GLOBALS['HookRanTestingAfter']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingHookAfter']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingHookAfter']));
   }
 
   /**
@@ -173,15 +177,17 @@ public function testHookAfter(): void {
    */
   public function testHookBefore(): void {
     $module_installer = $this->container->get('module_installer');
-    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically3']));
-    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically3']));
+    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically']));
+    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically']));
     $this->assertFalse(isset($GLOBALS['HookBefore']));
-    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingBefore']));
-    $this->assertFalse(isset($GLOBALS['HookRanTestingBefore']));
-    drupal_flush_all_caches();
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingHookBefore']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingHookBefore']));
+    $module_handler = $this->container->get('module_handler');
+    $data = ['hi'];
+    $module_handler->invokeAll('custom_hook_test_hook_before', $data);
     $this->assertTrue(isset($GLOBALS['HookBefore']));
-    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingBefore']));
-    $this->assertTrue(isset($GLOBALS['HookRanTestingBefore']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingHookBefore']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingHookBefore']));
   }
 
   /**
@@ -189,8 +195,8 @@ public function testHookBefore(): void {
    */
   public function testHookOrderGroup(): void {
     $module_installer = $this->container->get('module_installer');
-    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically4']));
-    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically4']));
+    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically']));
+    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically']));
     $this->assertFalse(isset($GLOBALS['HookOrderGroupExtraTypes']));
     $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingOrderGroupsExtraTypes']));
     $this->assertFalse(isset($GLOBALS['HookRanTestingOrderGroupsExtraTypes']));
@@ -212,15 +218,17 @@ public function testHookOrderGroup(): void {
    */
   public function testHookLast(): void {
     $module_installer = $this->container->get('module_installer');
-    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically5']));
-    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically5']));
+    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically']));
+    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically']));
     $this->assertFalse(isset($GLOBALS['HookLast']));
-    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingLast']));
-    $this->assertFalse(isset($GLOBALS['HookRanTestingLast']));
-    drupal_flush_all_caches();
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingHookLast']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingHookLast']));
+    $module_handler = $this->container->get('module_handler');
+    $data = ['hi'];
+    $module_handler->invokeAll('custom_hook_test_hook_last', $data);
     $this->assertTrue(isset($GLOBALS['HookLast']));
-    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingLast']));
-    $this->assertTrue(isset($GLOBALS['HookRanTestingLast']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingHookLast']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingHookLast']));
   }
 
 }
-- 
GitLab


From 065699d69fc41ed6020e2decd96ad622502e74e4 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Sat, 14 Dec 2024 16:58:21 -0500
Subject: [PATCH 063/173] Test ordering on a class and method

---
 ...second_order_first_alphabetically.info.yml |  7 ++++
 .../src/Hook/TestHookAfterClassMethod.php     | 33 +++++++++++++++++
 ..._second_order_last_alphabetically.info.yml |  7 ++++
 .../src/Hook/TestHookAfterClassMethod.php     | 35 +++++++++++++++++++
 .../Core/Hook/HookCollectorPassTest.php       | 18 ++++++++++
 5 files changed, 100 insertions(+)
 create mode 100644 core/modules/system/tests/modules/hook_second_order_first_alphabetically/hook_second_order_first_alphabetically.info.yml
 create mode 100644 core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php
 create mode 100644 core/modules/system/tests/modules/hook_second_order_last_alphabetically/hook_second_order_last_alphabetically.info.yml
 create mode 100644 core/modules/system/tests/modules/hook_second_order_last_alphabetically/src/Hook/TestHookAfterClassMethod.php

diff --git a/core/modules/system/tests/modules/hook_second_order_first_alphabetically/hook_second_order_first_alphabetically.info.yml b/core/modules/system/tests/modules/hook_second_order_first_alphabetically/hook_second_order_first_alphabetically.info.yml
new file mode 100644
index 000000000000..bd8dc0b2f73d
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_second_order_first_alphabetically/hook_second_order_first_alphabetically.info.yml
@@ -0,0 +1,7 @@
+name: first alphabetically
+type: module
+description: 'Test module used to test hook ordering.'
+package: Testing
+version: VERSION
+core_version_requirement: '*'
+hidden: true
diff --git a/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php b/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php
new file mode 100644
index 000000000000..8402e415bc6c
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php
@@ -0,0 +1,33 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_second_order_first_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Attribute\HookAfter;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering attribute.
+ */
+class TestHookAfterClassMethod {
+
+  /**
+   * This pair tests #[HookAfter] with a passed class and method.
+   */
+  #[HookAfter(['hook_second_order_last_alphabetically', 'TestHookAfterClassMethod::hookAfterClassMethod'])]
+  #[Hook('custom_hook_test_hook_after_class_method')]
+  public static function hookAfterClassMethod(): void {
+    $GLOBALS['HookAfterClassMethod'] = 'HookAfterMethod';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_second_order_last_alphabetically/hook_second_order_last_alphabetically.info.yml b/core/modules/system/tests/modules/hook_second_order_last_alphabetically/hook_second_order_last_alphabetically.info.yml
new file mode 100644
index 000000000000..aa08f259dc2e
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_second_order_last_alphabetically/hook_second_order_last_alphabetically.info.yml
@@ -0,0 +1,7 @@
+name: Hook ordering last
+type: module
+description: 'Test module used to test hook ordering.'
+package: Testing
+version: VERSION
+core_version_requirement: '*'
+hidden: true
diff --git a/core/modules/system/tests/modules/hook_second_order_last_alphabetically/src/Hook/TestHookAfterClassMethod.php b/core/modules/system/tests/modules/hook_second_order_last_alphabetically/src/Hook/TestHookAfterClassMethod.php
new file mode 100644
index 000000000000..dc3e6db8d2c3
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_second_order_last_alphabetically/src/Hook/TestHookAfterClassMethod.php
@@ -0,0 +1,35 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_second_order_last_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering attribute.
+ */
+class TestHookAfterClassMethod {
+
+  /**
+   * This pair tests #[HookAfter].
+   */
+  #[Hook('custom_hook_test_hook_after_class_method')]
+  public static function hookAfterClassMethod(): void {
+    // This should be run before so HookAfter should not be set.
+    if (isset($GLOBALS['HookAfterClassMethod'])) {
+      $GLOBALS['HookOutOfOrderTestingHookAfterClassMethod'] = 'HookOutOfOrderTestingHookAfterClassMethod';
+    }
+    $GLOBALS['HookRanTestingHookAfterClassMethod'] = 'HookRanTestingHookAfterClassMethod';
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
index 9db34afb478f..7109df1e9357 100644
--- a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
@@ -172,6 +172,24 @@ public function testHookAfter(): void {
     $this->assertTrue(isset($GLOBALS['HookRanTestingHookAfter']));
   }
 
+  /**
+   * Tests hook ordering with attributes.
+   */
+  public function testHookAfterClassMethod(): void {
+    $module_installer = $this->container->get('module_installer');
+    $this->assertTrue($module_installer->install(['hook_second_order_first_alphabetically']));
+    $this->assertTrue($module_installer->install(['hook_second_order_last_alphabetically']));
+    $this->assertFalse(isset($GLOBALS['HookAfterClassMethod']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingHookAfterClassMethod']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingHookAfterClassMethod']));
+    $module_handler = $this->container->get('module_handler');
+    $data = ['hi'];
+    $module_handler->invokeAll('custom_hook_test_hook_after_class_method', $data);
+    $this->assertTrue(isset($GLOBALS['HookAfterClassMethod']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingHookAfterClassMethod']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingHookAfterClassMethod']));
+  }
+
   /**
    * Tests hook ordering with attributes.
    */
-- 
GitLab


From 66aa79227c22a65587247b280a78d49d545d535f Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Sat, 14 Dec 2024 17:22:04 -0500
Subject: [PATCH 064/173] Rename variable for clarity and test reordering on
 class method

---
 core/lib/Drupal/Core/Hook/Attribute/HookAfter.php      |  7 +++----
 core/lib/Drupal/Core/Hook/Attribute/HookBefore.php     |  7 +++----
 core/lib/Drupal/Core/Hook/HookCollectorPass.php        | 10 +++++-----
 .../src/Hook/TestHookAfterClassMethod.php              |  3 ++-
 4 files changed, 13 insertions(+), 14 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
index 6e82bd17d848..ce92b5512836 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
@@ -17,12 +17,11 @@ class HookAfter extends HookOrderBase {
   /**
    * Constructs a HookAfter attribute.
    *
-   * @param array $modules
-   *   A list of things this implementation should run after. Each thing is
-   *   either a module name or a list of class and method.
+   * @param array $orderings
+   *   Each ordering is either a module name or a class and method pair array.
    */
   public function __construct(
-    public readonly array $modules,
+    public readonly array $orderings,
   ) {
     parent::__construct(FALSE);
   }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
index bde7335ea576..1c26d7db4171 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
@@ -17,12 +17,11 @@ class HookBefore extends HookOrderBase {
   /**
    * Constructs a HookBefore lib/Drupal/Core/Hook/Attribute/attribute.
    *
-   * @param array $modules
-   *   A list of things this implementation should run before. Each thing is
-   *   either a module name or a list of class and method.
+   * @param array $orderings
+   *   Each ordering is either a module name or a class and method pair array.
    */
   public function __construct(
-    public readonly array $modules,
+    public readonly array $orderings,
   ) {
     parent::__construct(TRUE);
   }
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index ead37f80d1a1..fa2e9f2551ff 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -242,15 +242,15 @@ protected static function reOrderServices(ContainerBuilder $container, array $al
       // ::process() adds the hook serving as key to the order group so it
       // does not need to be added if there's a group for the hook.
       $hooks = $orderGroups[$orderAttribute->hook] ?? [$orderAttribute->hook];
-      if (isset($orderAttribute->modules)) {
+      if (isset($orderAttribute->orderings)) {
         $others = [];
-        foreach ($orderAttribute->modules as $module) {
+        foreach ($orderAttribute->orderings as $ordering) {
           foreach ($hooks as $hook) {
-            if (is_array($module)) {
-              $others[] = $module;
+            if (is_array($ordering)) {
+              $others[] = $ordering;
             }
             else {
-              foreach ($implementations[$hook][$module] ?? [] as $class => $methods) {
+              foreach ($implementations[$hook][$ordering] ?? [] as $class => $methods) {
                 foreach ($methods as $method) {
                   $others[] = [$class, $method];
                 }
diff --git a/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php b/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php
index 8402e415bc6c..1752a787e9a6 100644
--- a/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php
+++ b/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php
@@ -6,6 +6,7 @@
 
 use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\Core\Hook\Attribute\HookAfter;
+use Drupal\hook_second_order_last_alphabetically\Hook\TestHookAfterClassMethod as TestHookAfterClassMethodForAfter;
 
 /**
  * Hook implementations for verifying ordering hooks by attributes.
@@ -24,7 +25,7 @@ class TestHookAfterClassMethod {
   /**
    * This pair tests #[HookAfter] with a passed class and method.
    */
-  #[HookAfter(['hook_second_order_last_alphabetically', 'TestHookAfterClassMethod::hookAfterClassMethod'])]
+  #[HookAfter([[TestHookAfterClassMethodForAfter::class, 'hookAfterClassMethod']])]
   #[Hook('custom_hook_test_hook_after_class_method')]
   public static function hookAfterClassMethod(): void {
     $GLOBALS['HookAfterClassMethod'] = 'HookAfterMethod';
-- 
GitLab


From 766ef1f3f62d89b411c1aa236ea1e6da8e0a3fc4 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Sat, 14 Dec 2024 18:43:19 -0500
Subject: [PATCH 065/173] Rename variables for consistency

---
 core/lib/Drupal/Core/Extension/ModuleHandler.php | 11 ++++-------
 core/lib/Drupal/Core/Hook/HookCollectorPass.php  | 12 ++++--------
 core/lib/Drupal/Core/Hook/HookPriority.php       |  2 --
 3 files changed, 8 insertions(+), 17 deletions(-)

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 712b7b3b4d0b..7c882c308452 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -80,13 +80,13 @@ class ModuleHandler implements ModuleHandlerInterface {
    *   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 $hooksOrderedByAttributes
+   * @param array $orderGroups
    *   An array of hooks that have been ordered by attributes.
    *
    * @see \Drupal\Core\DrupalKernel
    * @see \Drupal\Core\CoreServiceProvider
    */
-  public function __construct($root, array $module_list, protected EventDispatcherInterface $eventDispatcher, protected array $hookImplementationsMap, protected array $groupIncludes = [], protected array $hooksOrderedByAttributes = []) {
+  public function __construct($root, array $module_list, protected EventDispatcherInterface $eventDispatcher, protected array $hookImplementationsMap, protected array $groupIncludes = [], protected array $orderGroups = []) {
     $this->root = $root;
     $this->moduleList = [];
     foreach ($module_list as $name => $module) {
@@ -443,11 +443,8 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
         // used for ordering because the group might contain hooks not included
         // in this alter() call.
         foreach (array_merge($extra_hooks, [$type . '_alter']) as $extra_hook) {
-          if (isset($this->hooksOrderedByAttributes[$extra_hook])) {
-            $group = $this->hooksOrderedByAttributes[$extra_hook];
-            // When checking for already ordered groups ensure the listener
-            // is in the same order as when we set it.
-            krsort($group);
+          if (isset($this->orderGroups[$extra_hook])) {
+            $group = $this->orderGroups[$extra_hook];
             $extra_listeners = $this->findListenersForAlter(implode(':', $group));
             // Remove already ordered hooks.
             $extra_types = array_diff($extra_hooks, $group);
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index fa2e9f2551ff..adc96f9af1af 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -166,12 +166,12 @@ public function process(ContainerBuilder $container): array {
    *   All implementations.
    * @param array $legacyImplementations
    *   Modules that implement hooks.
-   * @param array $reorderGroups
+   * @param array $orderGroups
    *   Groups of hooks to reorder.
    *
    * @return void
    */
-  protected static function registerServices(ContainerBuilder $container, HookCollectorPass $collector, array $implementations, array $legacyImplementations, array $reorderGroups): void {
+  protected static function registerServices(ContainerBuilder $container, HookCollectorPass $collector, array $implementations, array $legacyImplementations, array $orderGroups): void {
     $container->register(ProceduralCall::class, ProceduralCall::class)
       ->addArgument($collector->includes);
     $groupIncludes = [];
@@ -184,7 +184,7 @@ protected static function registerServices(ContainerBuilder $container, HookColl
     }
 
     foreach ($legacyImplementations as $hook => $moduleImplements) {
-      $extraHooks = $reorderGroups[$hook] ?? [];
+      $extraHooks = $orderGroups[$hook] ?? [];
       foreach ($extraHooks as $extraHook) {
         $moduleImplements += $legacyImplementations[$extraHook] ?? [];
       }
@@ -212,13 +212,9 @@ protected static function registerServices(ContainerBuilder $container, HookColl
       }
     }
 
-    $hooksOrderedByAttributes = [];
-    foreach ($reorderGroups as $key => $values) {
-      $hooksOrderedByAttributes[$key] = $values;
-    }
     $definition = $container->getDefinition('module_handler');
     $definition->setArgument('$groupIncludes', $groupIncludes);
-    $definition->setArgument('$hooksOrderedByAttributes', $hooksOrderedByAttributes);
+    $definition->setArgument('$orderGroups', $orderGroups);
     $container->setParameter('hook_implementations_map', $map ?? []);
   }
 
diff --git a/core/lib/Drupal/Core/Hook/HookPriority.php b/core/lib/Drupal/Core/Hook/HookPriority.php
index 59bb9a89243e..b507d809dbff 100644
--- a/core/lib/Drupal/Core/Hook/HookPriority.php
+++ b/core/lib/Drupal/Core/Hook/HookPriority.php
@@ -39,8 +39,6 @@ public function change(array $hooks, HookOrderBase $attribute, ?array $others =
     }
     if (count($hooks) > 1) {
       $map = $this->container->getParameter('hook_implementations_map');
-      // Order the complex listener so we can find it runtime.
-      krsort($hooks);
       $combinedHookTag = implode(':', $hooks);
       $event = "drupal_hook.$combinedHookTag";
       $data = $others;
-- 
GitLab


From 0cb6c4735ad401fda9c35425920fe4b9a5bf45c8 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Sat, 14 Dec 2024 20:38:54 -0500
Subject: [PATCH 066/173] Unified approach

---
 core/lib/Drupal/Core/Hook/Attribute/Hook.php  |  10 ++
 core/lib/Drupal/Core/Hook/Attribute/Order.php |  20 +++
 .../Drupal/Core/Hook/Attribute/OrderType.php  |  19 +++
 .../Core/Hook/Attribute/SimpleOrderType.php   |  19 +++
 .../Drupal/Core/Hook/HookCollectorPass.php    | 119 ++++++------------
 core/lib/Drupal/Core/Hook/HookPriority.php    |  17 +--
 .../ckeditor5/src/Hook/Ckeditor5Hooks.php     |  12 +-
 7 files changed, 127 insertions(+), 89 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Hook/Attribute/Order.php
 create mode 100644 core/lib/Drupal/Core/Hook/Attribute/OrderType.php
 create mode 100644 core/lib/Drupal/Core/Hook/Attribute/SimpleOrderType.php

diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
index 1b220577a131..1d31055255fa 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -90,6 +90,8 @@
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
 class Hook {
 
+  public string $class = '';
+
   /**
    * Constructs a Hook attribute object.
    *
@@ -109,6 +111,7 @@ public function __construct(
     public string $hook,
     public string $method = '',
     public ?string $module = NULL,
+    public Order|SimpleOrderType|NULL $order = NULL,
   ) {}
 
   /**
@@ -117,10 +120,17 @@ public function __construct(
    * @param string $method
    *   The method that the hook attribute applies to.
    *   This only needs to be set when the attribute is on the class.
+   *
+   * @internal
    */
   public function setMethod(string $method): static {
     $this->method = $method;
     return $this;
   }
 
+  public function setClass(string $class): static {
+    $this->class = $class;
+    return $this;
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/Order.php b/core/lib/Drupal/Core/Hook/Attribute/Order.php
new file mode 100644
index 000000000000..6a2bbf8cd09e
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Attribute/Order.php
@@ -0,0 +1,20 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Attribute;
+
+class Order {
+
+  public function __construct(
+    public ?OrderType $type,
+    public ?array $modules = [],
+    public ?array $classesAndMethods = [],
+    public ?array $group = NULL,
+  ) {
+    if (!$this->modules && !$this->classesAndMethods) {
+      throw new \LogicException('Order against what?');
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Hook/Attribute/OrderType.php b/core/lib/Drupal/Core/Hook/Attribute/OrderType.php
new file mode 100644
index 000000000000..1855b54c6716
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Attribute/OrderType.php
@@ -0,0 +1,19 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Attribute;
+
+enum OrderType {
+
+  case Before;
+  case After;
+
+  public function shouldBeLast(): bool {
+    return match($this) {
+      OrderType::Before => TRUE,
+      OrderType::After => FALSE,
+    };
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Hook/Attribute/SimpleOrderType.php b/core/lib/Drupal/Core/Hook/Attribute/SimpleOrderType.php
new file mode 100644
index 000000000000..7957b1198df2
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Attribute/SimpleOrderType.php
@@ -0,0 +1,19 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Attribute;
+
+enum SimpleOrderType {
+
+  case First;
+  case Last;
+
+  public function shouldBeLast(): bool {
+    return match($this) {
+      SimpleOrderType::First => TRUE,
+      SimpleOrderType::Last => FALSE,
+    };
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index adc96f9af1af..6396e3e6eab2 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -9,9 +9,8 @@
 use Drupal\Component\FileCache\FileCacheFactory;
 use Drupal\Core\Extension\ProceduralCall;
 use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\HookOrderGroup;
-use Drupal\Core\Hook\Attribute\HookOrderInterface;
 use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\Core\Hook\Attribute\Order;
 use Drupal\Core\Hook\Attribute\StopProceduralHookScan;
 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -61,14 +60,9 @@ class HookCollectorPass implements CompilerPassInterface {
   /**
    * A list of attributes for hook implementations.
    *
-   * Keys are module, class and method. Values are all possible attributes on
-   * hook implementations: Hook to define the hook, HookOrderInterface to
-   * define the order in which the implementations fire, HookOrderGroup to
-   * define a group of hooks to be ordered together.
-   *
-   * @var array<string, <array string, <array string, Hook|HookOrderInterface|HookOrderGroup>>>
+   * Keys are module, class and method. values are Hook attributes.
    */
-  protected array $moduleAttributes = [];
+  protected array $moduleHooks = [];
 
   /**
    * {@inheritdoc}
@@ -77,66 +71,33 @@ public function process(ContainerBuilder $container): array {
     $collector = static::collectAllHookImplementations($container->getParameter('container.modules'), $container);
     $implementations = [];
     $orderGroups = [];
-    /** @var \Drupal\Core\Hook\Attribute\HookOrderBase[] $allOrderAttributes */
-    $allOrderAttributes = [];
+    $orderAttributes = [];
     foreach (array_keys($container->getParameter('container.modules')) as $module) {
-      foreach ($collector->moduleAttributes[$module] ?? [] as $class => $methods) {
-        foreach ($methods as $method => $attributes) {
-          $orderAttributes = [];
-          $orderGroup = FALSE;
-          $hookAttributes = [];
-          foreach ($attributes as $attribute) {
-            switch (TRUE) {
-              case $attribute instanceof Hook:
-                if ($class !== ProceduralCall::class) {
-                  self::checkForProceduralOnlyHooks($attribute, $class);
-                }
-                if (!$attribute->module) {
-                  $attribute->module = $module;
-                }
-                if (!$attribute->method) {
-                  $attribute->method = $method;
-                }
-                $hookAttributes[] = $attribute;
-                $legacyImplementations[$attribute->hook][$attribute->module] = '';
-                $implementations[$attribute->hook][$attribute->module][$class][] = $attribute->method;
-                break;
-
-              case $attribute instanceof HookOrderInterface:
-                $orderAttributes[] = $attribute;
-                break;
-
-              case $attribute instanceof HookOrderGroup:
-                $orderGroup = $attribute->group;
-                break;
+      foreach ($collector->moduleHooks[$module] ?? [] as $class => $methods) {
+        foreach ($methods as $method => $hooks) {
+          foreach ($hooks as $hook) {
+            assert($hook instanceof Hook);
+            $hook->setClass($class);
+            if ($class !== ProceduralCall::class) {
+              self::checkForProceduralOnlyHooks($hook);
             }
-          }
-          // If no ordering is required the processing of this method is done.
-          if (!$orderAttributes) {
-            if ($orderGroup) {
-              throw new \LogicException('HookOrderGroup requires an order to be specified.');
+            if (!$hook->module) {
+              $hook->module = $module;
             }
-            continue;
-          }
-          // Now process ordering, if possible.
-          if (!$hooksCount = count($hookAttributes)) {
-            throw new \LogicException('Order attributes require a Hook attribute.');
-          }
-          if ($hooksCount === 1) {
-            $hookAttribute = reset($hookAttributes);
-            foreach ($orderAttributes as $orderAttribute) {
-              $allOrderAttributes[] = $orderAttribute->set(hook: $hookAttribute, class: $class);
+            if (!$hook->method) {
+              $hook->method = $method;
             }
-            if ($orderGroup) {
-              $orderGroup[] = $hookAttribute->hook;
-              foreach ($orderGroup as $extraHook) {
-                $orderGroups[$extraHook] = array_merge($orderGroups[$extraHook] ?? [], $orderGroup);
+            $legacyImplementations[$hook->hook][$hook->module] = '';
+            $implementations[$hook->hook][$hook->module][$class][] = $hook->method;
+            if ($hook->order) {
+              $orderAttributes[] = $hook;
+              if ($hook->order instanceof Order && $hook->order->group) {
+                foreach ($hook->order->group as $extraHook) {
+                  $orderGroups[$extraHook] = array_merge($orderGroups[$extraHook] ?? [], $hook->order->group);
+                }
               }
             }
           }
-          else {
-            throw new \LogicException('Hook ordering can only be applied to methods with one Hook attribute.');
-          }
         }
       }
     }
@@ -147,7 +108,7 @@ public function process(ContainerBuilder $container): array {
     // @see https://www.drupal.org/project/drupal/issues/3481778
     if (count($container->getDefinitions()) > 1) {
       static::registerServices($container, $collector, $implementations, $legacyImplementations ?? [], $orderGroups);
-      static::reOrderServices($container, $allOrderAttributes, $orderGroups, $implementations);
+      static::reOrderServices($container, $orderAttributes, $orderGroups, $implementations);
     }
     return $implementations;
   }
@@ -223,7 +184,7 @@ protected static function registerServices(ContainerBuilder $container, HookColl
    *
    * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
    *   The container.
-   * @param array $allOrderAttributes
+   * @param array $orderAttributes
    *   All attributes related to ordering.
    * @param array $orderGroups
    *   Groups to order by.
@@ -232,28 +193,25 @@ protected static function registerServices(ContainerBuilder $container, HookColl
    *
    * @return void
    */
-  protected static function reOrderServices(ContainerBuilder $container, array $allOrderAttributes, array $orderGroups, array $implementations): void {
+  protected static function reOrderServices(ContainerBuilder $container, array $orderAttributes, array $orderGroups, array $implementations): void {
     $hookPriority = new HookPriority($container);
-    foreach ($allOrderAttributes as $orderAttribute) {
+    foreach ($orderAttributes as $orderAttribute) {
+      assert($orderAttribute instanceof Hook);
       // ::process() adds the hook serving as key to the order group so it
       // does not need to be added if there's a group for the hook.
       $hooks = $orderGroups[$orderAttribute->hook] ?? [$orderAttribute->hook];
-      if (isset($orderAttribute->orderings)) {
+      if ($orderAttribute->order instanceof Order) {
         $others = [];
-        foreach ($orderAttribute->orderings as $ordering) {
+        foreach ($orderAttribute->order->modules as $modules) {
           foreach ($hooks as $hook) {
-            if (is_array($ordering)) {
-              $others[] = $ordering;
-            }
-            else {
-              foreach ($implementations[$hook][$ordering] ?? [] as $class => $methods) {
-                foreach ($methods as $method) {
-                  $others[] = [$class, $method];
-                }
+            foreach ($implementations[$hook][$modules] ?? [] as $class => $methods) {
+              foreach ($methods as $method) {
+                $others[] = [$class, $method];
               }
             }
           }
         }
+        $others = array_merge($others, $orderAttribute->order->classesAndMethods);
       }
       else {
         $others = NULL;
@@ -348,7 +306,7 @@ protected function collectModuleHookImplementations($dir, $module, $module_preg,
             $hook_file_cache->set($filename, ['class' => $class, 'attributes' => $attributes]);
           }
         }
-        $this->moduleAttributes[$module][$class] = $attributes;
+        $this->moduleHooks[$module][$class] = $attributes;
       }
       elseif (!$skip_procedural) {
         $implementations = $procedural_hook_file_cache->get($filename);
@@ -412,7 +370,7 @@ protected static function filterIterator(\SplFileInfo $fileInfo, $key, \Recursiv
    *   The name of function implementing the hook.
    */
   protected function addProceduralImplementation(\SplFileInfo $fileinfo, string $hook, string $module, string $function): void {
-    $this->moduleAttributes[$module][ProceduralCall::class][$function] = [new Hook($hook, $module . '_' . $hook)];
+    $this->moduleHooks[$module][ProceduralCall::class][$function] = [new Hook($hook, $module . '_' . $hook)];
     if ($hook === 'hook_info') {
       $this->hookInfo[] = $function;
     }
@@ -460,7 +418,7 @@ public function getImplementations($paths): array {
    * @param string $class
    *   The class the hook is implemented on.
    */
-  public static function checkForProceduralOnlyHooks(Hook $hook, string $class): void {
+  public static function checkForProceduralOnlyHooks(Hook $hook, string $class = ''): void {
     $staticDenyHooks = [
       'hook_info',
       'install',
@@ -474,6 +432,9 @@ public static function checkForProceduralOnlyHooks(Hook $hook, string $class): v
     ];
 
     if (in_array($hook->hook, $staticDenyHooks) || preg_match('/^(post_update_|preprocess_|update_\d+$)/', $hook->hook)) {
+      if (!$class) {
+        $class = $hook->class;
+      }
       throw new \LogicException("The hook $hook->hook on class $class does not support attributes and must remain procedural.");
     }
   }
diff --git a/core/lib/Drupal/Core/Hook/HookPriority.php b/core/lib/Drupal/Core/Hook/HookPriority.php
index b507d809dbff..775610f98ec1 100644
--- a/core/lib/Drupal/Core/Hook/HookPriority.php
+++ b/core/lib/Drupal/Core/Hook/HookPriority.php
@@ -4,7 +4,9 @@
 
 namespace Drupal\Core\Hook;
 
+use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\Core\Hook\Attribute\HookOrderBase;
+use Drupal\Core\Hook\Attribute\SimpleOrderType;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 
 /**
@@ -32,8 +34,8 @@ public function __construct(protected ContainerBuilder $container) {}
    *
    * @internal
    */
-  public function change(array $hooks, HookOrderBase $attribute, ?array $others = NULL): void {
-    $class_and_method = "$attribute->class::$attribute->method";
+  public function change(array $hooks, Hook $hook, ?array $others = NULL): void {
+    $class_and_method = "$hook->class::$hook->method";
     if ($others) {
       $other_specifiers = array_map(fn ($pair) => $pair[0] . '::' . $pair[1], $others);
     }
@@ -42,7 +44,7 @@ public function change(array $hooks, HookOrderBase $attribute, ?array $others =
       $combinedHookTag = implode(':', $hooks);
       $event = "drupal_hook.$combinedHookTag";
       $data = $others;
-      $data[] = [$attribute->class, $attribute->method];
+      $data[] = [$hook->class, $hook->method];
       $priority = 0;
       foreach ($data as [$class, $method]) {
         foreach ($hooks as $hook) {
@@ -84,18 +86,19 @@ public function change(array $hooks, HookOrderBase $attribute, ?array $others =
     if (!isset($index_this) || !isset($priorities) || !isset($priorities_other)) {
       return;
     }
+    $shouldBeLarger = $hook->order instanceof SimpleOrderType ? $hook->order->shouldBeLast() : $hook->order->type->shouldBeLast();
     // The priority of the hook being changed.
     $priority_this = $priorities[$index_this];
     // The priority of the hook being compared to.
-    $priority_other = $attribute->shouldBeLarger ? max($priorities_other) : min($priorities_other);
+    $priority_other = $shouldBeLarger ? max($priorities_other) : min($priorities_other);
     // If the order is correct there is nothing to do. If the two priorities
     // are the same then the order is undefined and so it can't be correct.
     // If they are not the same and $priority_this is already larger exactly
     // when $attribute->shouldBeLarger says then it's the correct order.
-    if ($priority_this !== $priority_other && ($attribute->shouldBeLarger === ($priority_this > $priority_other))) {
+    if ($priority_this !== $priority_other && ($shouldBeLarger === ($priority_this > $priority_other))) {
       return;
     }
-    $priority_new = $priority_other + ($attribute->shouldBeLarger ? 1 : -1);
+    $priority_new = $priority_other + ($shouldBeLarger ? 1 : -1);
     // For first and last this new priority is already larger/smaller
     // than all existing priorities but for before / after it might belong to
     // an already existing hook. In this case set the new priority temporarily
@@ -105,7 +108,7 @@ public function change(array $hooks, HookOrderBase $attribute, ?array $others =
     // relative to both $priority_other and the hook whose priority was
     // $priority_new.
     if (in_array($priority_new, $priorities)) {
-      $priorities[$index_this] = $priority_other + ($attribute->shouldBeLarger ? 0.5 : -0.5);
+      $priorities[$index_this] = $priority_other + ($shouldBeLarger ? 0.5 : -0.5);
       asort($priorities);
       $changed_indexes = array_keys($priorities);
       $priorities = array_combine($changed_indexes, range(1, count($changed_indexes)));
diff --git a/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php b/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php
index fd6c2dc3cb16..c28145c95896 100644
--- a/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php
+++ b/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php
@@ -3,6 +3,8 @@
 namespace Drupal\ckeditor5\Hook;
 
 use Drupal\Core\Hook\Attribute\HookAfter;
+use Drupal\Core\Hook\Attribute\Order;
+use Drupal\Core\Hook\Attribute\OrderType;
 use Drupal\Core\Hook\Attribute\HookOrderGroup;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Asset\AttachedAssetsInterface;
@@ -110,9 +112,13 @@ public function theme() : array {
    * 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')]
-  #[HookOrderGroup(['form_filter_format_add_form_alter', 'form_filter_format_edit_form_alter'])]
-  #[HookAfter(['editor', 'media'])]
+  #[Hook('form_filter_format_form_alter',
+    order: new Order(
+      type: OrderType::After,
+      modules: ['editor', 'media'],
+      group: ['form_filter_format_add_form_alter', 'form_filter_format_edit_form_alter'],
+    )
+  )]
   public function formFilterFormatFormAlter(array &$form, FormStateInterface $form_state, $form_id) : void {
     $editor = $form_state->get('editor');
     // CKEditor 5 plugin config determines the available HTML tags. If an HTML
-- 
GitLab


From 36541e41e646487a42ccc57f0741da097e0016ef Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Sat, 14 Dec 2024 20:53:40 -0500
Subject: [PATCH 067/173] Round 2

---
 core/lib/Drupal/Core/Hook/Attribute/Hook.php  | 19 -------------------
 .../Drupal/Core/Hook/Attribute/OrderType.php  | 13 +++----------
 .../Core/Hook/Attribute/SimpleOrderType.php   | 13 +++----------
 .../Drupal/Core/Hook/HookCollectorPass.php    |  2 +-
 core/lib/Drupal/Core/Hook/HookPriority.php    |  7 +++----
 5 files changed, 10 insertions(+), 44 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
index 1d31055255fa..2804a72ce795 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -114,23 +114,4 @@ public function __construct(
     public Order|SimpleOrderType|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.
-   *
-   * @internal
-   */
-  public function setMethod(string $method): static {
-    $this->method = $method;
-    return $this;
-  }
-
-  public function setClass(string $class): static {
-    $this->class = $class;
-    return $this;
-  }
-
 }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/OrderType.php b/core/lib/Drupal/Core/Hook/Attribute/OrderType.php
index 1855b54c6716..fcb054c1d250 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/OrderType.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/OrderType.php
@@ -4,16 +4,9 @@
 
 namespace Drupal\Core\Hook\Attribute;
 
-enum OrderType {
+enum OrderType: int {
 
-  case Before;
-  case After;
-
-  public function shouldBeLast(): bool {
-    return match($this) {
-      OrderType::Before => TRUE,
-      OrderType::After => FALSE,
-    };
-  }
+  case Before = 1;
+  case After = 0;
 
 }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/SimpleOrderType.php b/core/lib/Drupal/Core/Hook/Attribute/SimpleOrderType.php
index 7957b1198df2..95c2d7352cda 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/SimpleOrderType.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/SimpleOrderType.php
@@ -4,16 +4,9 @@
 
 namespace Drupal\Core\Hook\Attribute;
 
-enum SimpleOrderType {
+enum SimpleOrderType: int {
 
-  case First;
-  case Last;
-
-  public function shouldBeLast(): bool {
-    return match($this) {
-      SimpleOrderType::First => TRUE,
-      SimpleOrderType::Last => FALSE,
-    };
-  }
+  case First = 1;
+  case Last = 0;
 
 }
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 6396e3e6eab2..5a54b6ca2fca 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -77,7 +77,7 @@ public function process(ContainerBuilder $container): array {
         foreach ($methods as $method => $hooks) {
           foreach ($hooks as $hook) {
             assert($hook instanceof Hook);
-            $hook->setClass($class);
+            $hook->class = $class;
             if ($class !== ProceduralCall::class) {
               self::checkForProceduralOnlyHooks($hook);
             }
diff --git a/core/lib/Drupal/Core/Hook/HookPriority.php b/core/lib/Drupal/Core/Hook/HookPriority.php
index 775610f98ec1..4326fb7cebd6 100644
--- a/core/lib/Drupal/Core/Hook/HookPriority.php
+++ b/core/lib/Drupal/Core/Hook/HookPriority.php
@@ -5,7 +5,6 @@
 namespace Drupal\Core\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\HookOrderBase;
 use Drupal\Core\Hook\Attribute\SimpleOrderType;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 
@@ -26,8 +25,8 @@ public function __construct(protected ContainerBuilder $container) {}
    *   in Drupal\Core\Hook\Attribute, and it might also contain
    *   the hooks listed in the Drupal\Core\Hook\Attribute\HookOrderGroup
    *   attribute.
-   * @param \Drupal\Core\Hook\Attribute\HookOrderBase $attribute
-   *   The order attribute.
+   * @param \Drupal\Core\Hook\Attribute\Hook $hook
+   *   The hook attribute.
    * @param array|null $others
    *   Other hook implementations to compare to, if any. The array is a list of
    *   class and method pairs.
@@ -86,7 +85,7 @@ public function change(array $hooks, Hook $hook, ?array $others = NULL): void {
     if (!isset($index_this) || !isset($priorities) || !isset($priorities_other)) {
       return;
     }
-    $shouldBeLarger = $hook->order instanceof SimpleOrderType ? $hook->order->shouldBeLast() : $hook->order->type->shouldBeLast();
+    $shouldBeLarger = boolval($hook->order instanceof SimpleOrderType ? $hook->order->value : $hook->order->type->value);
     // The priority of the hook being changed.
     $priority_this = $priorities[$index_this];
     // The priority of the hook being compared to.
-- 
GitLab


From 0718322eef371bc43a99ee8190f5b4122efe00de Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Sat, 14 Dec 2024 21:12:48 -0500
Subject: [PATCH 068/173] Priority update

---
 core/lib/Drupal/Core/Hook/Attribute/Hook.php           | 10 ++++++++++
 core/lib/Drupal/Core/Hook/Attribute/Order.php          |  8 ++++----
 core/lib/Drupal/Core/Hook/HookCollectorPass.php        | 10 ++--------
 core/lib/Drupal/Core/Hook/HookPriority.php             |  2 +-
 .../src/Hook/TestHookAfter.php                         | 10 +++++++---
 5 files changed, 24 insertions(+), 16 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
index 2804a72ce795..1384b992f5e4 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -114,4 +114,14 @@ public function __construct(
     public Order|SimpleOrderType|NULL $order = NULL,
   ) {}
 
+  public function set(string $class, string $module, string $method): void {
+    $this->class = $class;
+    if (!$this->module) {
+      $this->module = $module;
+    }
+    if (!$this->method) {
+      $this->method = $method;
+    }
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/Order.php b/core/lib/Drupal/Core/Hook/Attribute/Order.php
index 6a2bbf8cd09e..d8c97659df96 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Order.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Order.php
@@ -7,10 +7,10 @@
 class Order {
 
   public function __construct(
-    public ?OrderType $type,
-    public ?array $modules = [],
-    public ?array $classesAndMethods = [],
-    public ?array $group = NULL,
+    public OrderType $type,
+    public array $modules = [],
+    public array $classesAndMethods = [],
+    public array $group = [],
   ) {
     if (!$this->modules && !$this->classesAndMethods) {
       throw new \LogicException('Order against what?');
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 5a54b6ca2fca..9d38f355423f 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -77,21 +77,15 @@ public function process(ContainerBuilder $container): array {
         foreach ($methods as $method => $hooks) {
           foreach ($hooks as $hook) {
             assert($hook instanceof Hook);
-            $hook->class = $class;
+            $hook->set(class: $class, module: $module, method: $method);
             if ($class !== ProceduralCall::class) {
               self::checkForProceduralOnlyHooks($hook);
             }
-            if (!$hook->module) {
-              $hook->module = $module;
-            }
-            if (!$hook->method) {
-              $hook->method = $method;
-            }
             $legacyImplementations[$hook->hook][$hook->module] = '';
             $implementations[$hook->hook][$hook->module][$class][] = $hook->method;
             if ($hook->order) {
               $orderAttributes[] = $hook;
-              if ($hook->order instanceof Order && $hook->order->group) {
+              if ($hook->order instanceof Order) {
                 foreach ($hook->order->group as $extraHook) {
                   $orderGroups[$extraHook] = array_merge($orderGroups[$extraHook] ?? [], $hook->order->group);
                 }
diff --git a/core/lib/Drupal/Core/Hook/HookPriority.php b/core/lib/Drupal/Core/Hook/HookPriority.php
index 4326fb7cebd6..a0b8866c3636 100644
--- a/core/lib/Drupal/Core/Hook/HookPriority.php
+++ b/core/lib/Drupal/Core/Hook/HookPriority.php
@@ -93,7 +93,7 @@ public function change(array $hooks, Hook $hook, ?array $others = NULL): void {
     // If the order is correct there is nothing to do. If the two priorities
     // are the same then the order is undefined and so it can't be correct.
     // If they are not the same and $priority_this is already larger exactly
-    // when $attribute->shouldBeLarger says then it's the correct order.
+    // when $shouldBeLarger says then it's the correct order.
     if ($priority_this !== $priority_other && ($shouldBeLarger === ($priority_this > $priority_other))) {
       return;
     }
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php
index 49748e2eb9fa..6efe80367e48 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php
@@ -6,6 +6,8 @@
 
 use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\Core\Hook\Attribute\HookAfter;
+use Drupal\Core\Hook\Attribute\Order;
+use Drupal\Core\Hook\Attribute\OrderType;
 
 /**
  * Hook implementations for verifying ordering hooks by attributes.
@@ -22,10 +24,12 @@
 class TestHookAfter {
 
   /**
-   * This pair tests #[HookAfter].
+   * This pair tests OrderType::After.
    */
-  #[HookAfter(['hook_order_last_alphabetically'])]
-  #[Hook('custom_hook_test_hook_after')]
+  #[Hook('custom_hook_test_hook_after', order: new Order(
+    type: OrderType::After,
+    modules: ['hook_order_last_alphabetically']
+  ))]
   public static function hookAfter(): void {
     $GLOBALS['HookAfter'] = 'HookAfter';
   }
-- 
GitLab


From af301fda81968cc4fb30d4eb879a1debb7868b59 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Sat, 14 Dec 2024 21:13:46 -0500
Subject: [PATCH 069/173] Remove attributes that are not needed

---
 .../Drupal/Core/Hook/Attribute/HookAfter.php  | 29 -----------
 .../Drupal/Core/Hook/Attribute/HookBefore.php | 29 -----------
 .../Drupal/Core/Hook/Attribute/HookFirst.php  | 24 ---------
 .../Drupal/Core/Hook/Attribute/HookLast.php   | 24 ---------
 .../Core/Hook/Attribute/HookOrderBase.php     | 51 -------------------
 .../Core/Hook/Attribute/HookOrderGroup.php    | 44 ----------------
 .../Hook/Attribute/HookOrderInterface.php     | 22 --------
 7 files changed, 223 deletions(-)
 delete mode 100644 core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
 delete mode 100644 core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
 delete mode 100644 core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
 delete mode 100644 core/lib/Drupal/Core/Hook/Attribute/HookLast.php
 delete mode 100644 core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
 delete mode 100644 core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php
 delete mode 100644 core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php

diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php b/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
deleted file mode 100644
index ce92b5512836..000000000000
--- a/core/lib/Drupal/Core/Hook/Attribute/HookAfter.php
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Core\Hook\Attribute;
-
-/**
- * Attribute to request this hook implementation to fire after others.
- *
- * @section sec_backwards_compatibility Backwards-compatibility
- *
- * @see HookOrderGroup
- */
-#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
-class HookAfter extends HookOrderBase {
-
-  /**
-   * Constructs a HookAfter attribute.
-   *
-   * @param array $orderings
-   *   Each ordering is either a module name or a class and method pair array.
-   */
-  public function __construct(
-    public readonly array $orderings,
-  ) {
-    parent::__construct(FALSE);
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php b/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
deleted file mode 100644
index 1c26d7db4171..000000000000
--- a/core/lib/Drupal/Core/Hook/Attribute/HookBefore.php
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Core\Hook\Attribute;
-
-/**
- * Attribute to request this hook implementation to fire before others.
- *
- * @section sec_backwards_compatibility Backwards-compatibility
- *
- * @see HookOrderGroup
- */
-#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
-class HookBefore extends HookOrderBase {
-
-  /**
-   * Constructs a HookBefore lib/Drupal/Core/Hook/Attribute/attribute.
-   *
-   * @param array $orderings
-   *   Each ordering is either a module name or a class and method pair array.
-   */
-  public function __construct(
-    public readonly array $orderings,
-  ) {
-    parent::__construct(TRUE);
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php b/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
deleted file mode 100644
index b7d82aab5d13..000000000000
--- a/core/lib/Drupal/Core/Hook/Attribute/HookFirst.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Core\Hook\Attribute;
-
-/**
- * Attribute to request this hook implementation to fire first.
- *
- * @section sec_backwards_compatibility Backwards-compatibility
- *
- * @see HookOrderGroup
- */
-#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
-class HookFirst extends HookOrderBase {
-
-  /**
-   * Constructs a HookFirst attribute.
-   */
-  public function __construct() {
-    parent::__construct(TRUE);
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookLast.php b/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
deleted file mode 100644
index d5c92d00eb77..000000000000
--- a/core/lib/Drupal/Core/Hook/Attribute/HookLast.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Core\Hook\Attribute;
-
-/**
- * Attribute to request this hook implementation to fire after others.
- *
- * @section sec_backwards_compatibility Backwards-compatibility
- *
- * @see HookOrderGroup
- */
-#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
-class HookLast extends HookOrderBase {
-
-  /**
-   * Constructs a HookLast attribute.
-   */
-  public function __construct() {
-    parent::__construct(FALSE);
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
deleted file mode 100644
index f7297b3486e3..000000000000
--- a/core/lib/Drupal/Core/Hook/Attribute/HookOrderBase.php
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Core\Hook\Attribute;
-
-/**
- * Common set of functionality needed by attributes that handle ordering hooks.
- */
-class HookOrderBase implements HookOrderInterface {
-
-  /**
-   * The hook that should be ordered.
-   *
-   * @internal
-   */
-  public string $hook;
-
-  /**
-   * The class the hook is found in.
-   *
-   * @internal
-   */
-  public string $class;
-
-  /**
-   * The method of the hook.
-   *
-   * @internal
-   */
-  public string $method;
-
-  /**
-   * Constructs a HookOrderBase class.
-   *
-   * @param bool $shouldBeLarger
-   *   Determines whether the hook should increase or decrease priority.
-   */
-  public function __construct(public readonly bool $shouldBeLarger) {}
-
-  /**
-   * {@inheritdoc}
-   */
-  public function set(Hook $hook, string $class): static {
-    $this->hook = $hook->hook;
-    $this->class = $class;
-    $this->method = $hook->method;
-    return $this;
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php
deleted file mode 100644
index c4f45c6728b1..000000000000
--- a/core/lib/Drupal/Core/Hook/Attribute/HookOrderGroup.php
+++ /dev/null
@@ -1,44 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Core\Hook\Attribute;
-
-/**
- * List of alter hooks called together.
- *
- * Ordering by attributes happens at build time by setting up the order of
- * the listeners of a hook correctly. However, ModuleHandlerInterface::alter()
- * can be called with multiple hooks runtime. If the hook defined on this
- * method/class requires ordering relative to other such hooks then this
- * attribute can be used to order relative to implementations of all hooks in
- * the group. Include all alter hooks to be ordered against in the group even
- * if no single alter() call includes all of them. For example, this can be
- * used to order a hook_form_BASE_FORM_ID_alter() implementation relative to
- * multiple hook_form_FORM_ID_alter() implementations as
- * Drupal\ckeditor5\Hook\Ckeditor5Hooks::formFilterFormatFormAlter() does.
- *
- * @section sec_backwards_compatibility Backwards-compatibility
- *
- * To allow hook implementations to work on older versions of Drupal as well,
- * keep the hook_module_implements_alter() implementation and execute the
- * same ordering as prescribed by the hook order attributes. Then add
- * #[LegacyHook] to the hook_module_implements_alter() implementation so it
- * only gets executed in older Drupal versions.
- *
- * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
- */
-#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD)]
-class HookOrderGroup {
-
-  /**
-   * Constructs a HookOrderGroup attribute object.
-   *
-   * @param array $group
-   *   A list of hooks to sort together.
-   */
-  public function __construct(
-    public readonly array $group,
-  ) {}
-
-}
diff --git a/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php b/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
deleted file mode 100644
index edd5be7186e9..000000000000
--- a/core/lib/Drupal/Core/Hook/Attribute/HookOrderInterface.php
+++ /dev/null
@@ -1,22 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Core\Hook\Attribute;
-
-/**
- * Interface for classes that manage hook ordering.
- */
-interface HookOrderInterface {
-
-  /**
-   * Set the properties on the attributes using this class.
-   *
-   * @param \Drupal\Core\Hook\Attribute\Hook $hook
-   *   The hook attribute to order.
-   * @param string $class
-   *   The class the hook is in.
-   */
-  public function set(Hook $hook, string $class): static;
-
-}
-- 
GitLab


From 806ebbda0c80733da62ab2ce4a92e2dbb9f98b66 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Sat, 14 Dec 2024 21:37:21 -0500
Subject: [PATCH 070/173] Update tests

---
 core/lib/Drupal/Core/Hook/Attribute/Order.php    |  2 +-
 .../src/Hook/TestHookAfter.php                   |  1 -
 .../src/Hook/TestHookBefore.php                  |  2 +-
 .../src/Hook/TestHookFirst.php                   |  2 +-
 .../src/Hook/TestHookLast.php                    |  9 ++++-----
 .../src/Hook/TestHookOrderGroup.php              | 16 ++++++++++------
 .../src/Hook/TestHookAfter.php                   |  2 +-
 .../src/Hook/TestHookBefore.php                  | 11 +++++++----
 .../src/Hook/TestHookFirst.php                   |  7 +++----
 .../src/Hook/TestHookLast.php                    |  2 +-
 .../src/Hook/TestHookOrderGroup.php              |  2 +-
 .../src/Hook/TestHookAfterClassMethod.php        | 13 +++++++++----
 12 files changed, 39 insertions(+), 30 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/Order.php b/core/lib/Drupal/Core/Hook/Attribute/Order.php
index d8c97659df96..ebd1cd025c69 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Order.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Order.php
@@ -13,7 +13,7 @@ public function __construct(
     public array $group = [],
   ) {
     if (!$this->modules && !$this->classesAndMethods) {
-      throw new \LogicException('Order against what?');
+      throw new \LogicException('Order must provide elements to order against.');
     }
   }
 
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php
index 6efe80367e48..15fbb62434da 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php
@@ -5,7 +5,6 @@
 namespace Drupal\hook_order_first_alphabetically\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\HookAfter;
 use Drupal\Core\Hook\Attribute\Order;
 use Drupal\Core\Hook\Attribute\OrderType;
 
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookBefore.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookBefore.php
index 6ac7468053d6..c83a3a89f0ce 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookBefore.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookBefore.php
@@ -21,7 +21,7 @@
 class TestHookBefore {
 
   /**
-   * This pair tests #[HookBefore].
+   * This pair tests OrderType::Before.
    */
   #[Hook('custom_hook_test_hook_before')]
   public static function hookBefore(): void {
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookFirst.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookFirst.php
index 012e3f0d2c54..a1bc165a33ff 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookFirst.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookFirst.php
@@ -21,7 +21,7 @@
 class TestHookFirst {
 
   /**
-   * This pair tests #[HookFirst].
+   * This pair tests OrderType::First.
    */
   #[Hook('custom_hook_test_hook_first')]
   public static function hookFirst(): void {
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php
index 0a82ba6e55b3..7655b6bed92f 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php
@@ -5,7 +5,7 @@
 namespace Drupal\hook_order_first_alphabetically\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\HookLast;
+use Drupal\Core\Hook\Attribute\SimpleOrderType;
 
 /**
  * Hook implementations for verifying ordering hooks by attributes.
@@ -21,11 +21,10 @@
  */
 class TestHookLast {
 
-  /**
-   * This pair tests #[HookLast].
+    /**
+   * This pair tests OrderType::Last.
    */
-  #[HookLast]
-  #[Hook('custom_hook_test_hook_last')]
+  #[Hook('custom_hook_test_hook_last', order: SimpleOrderType::Last)]
   public static function hookLast(): void {
     $GLOBALS['HookLast'] = 'HookLast';
   }
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderGroup.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderGroup.php
index be8b4274cc26..bc21ff1a3ea6 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderGroup.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderGroup.php
@@ -5,8 +5,8 @@
 namespace Drupal\hook_order_first_alphabetically\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\HookAfter;
-use Drupal\Core\Hook\Attribute\HookOrderGroup;
+use Drupal\Core\Hook\Attribute\Order;
+use Drupal\Core\Hook\Attribute\OrderType;
 
 /**
  * Hook implementations for verifying ordering hooks by attributes.
@@ -23,11 +23,15 @@
 class TestHookOrderGroup {
 
   /**
-   * This pair tests #[HookOrderGroup].
+   * This pair tests OrderType::After with Group.
    */
-  #[HookAfter(['hook_order_last_alphabetically'])]
-  #[HookOrderGroup(['custom_hook_extra_types2_alter'])]
-  #[Hook('custom_hook_extra_types1_alter')]
+  #[Hook('custom_hook_extra_types1_alter',
+    order: new Order(
+      type: OrderType::After,
+      modules: ['hook_order_last_alphabetically'],
+      group: ['custom_hook_extra_types2_alter'],
+    )
+  )]
   public static function customHookExtraTypes(): void {
     // This should be run after so HookOrderGroupExtraTypes should not be set.
     if (!isset($GLOBALS['HookOrderGroupExtraTypes'])) {
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookAfter.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookAfter.php
index c01e15b71f61..e15ca55aea6c 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookAfter.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookAfter.php
@@ -21,7 +21,7 @@
 class TestHookAfter {
 
   /**
-   * This pair tests #[HookAfter].
+   * This pair tests OrderType::After.
    */
   #[Hook('custom_hook_test_hook_after')]
   public static function hookAfter(): void {
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookBefore.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookBefore.php
index 946377092261..99934423ea48 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookBefore.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookBefore.php
@@ -5,7 +5,8 @@
 namespace Drupal\hook_order_last_alphabetically\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\HookBefore;
+use Drupal\Core\Hook\Attribute\Order;
+use Drupal\Core\Hook\Attribute\OrderType;
 
 /**
  * Hook implementations for verifying ordering hooks by attributes.
@@ -22,10 +23,12 @@
 class TestHookBefore {
 
   /**
-   * This pair tests #[HookBefore].
+   * This pair tests OrderType::Before.
    */
-  #[HookBefore(['hook_order_first_alphabetically'])]
-  #[Hook('custom_hook_test_hook_before')]
+  #[Hook('custom_hook_test_hook_before', order: new Order(
+    type: OrderType::Before,
+    modules: ['hook_order_first_alphabetically']
+  ))]
   public static function cacheFlush(): void {
     $GLOBALS['HookBefore'] = 'HookBefore';
   }
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookFirst.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookFirst.php
index 3980b23bfb85..06972d5c97d0 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookFirst.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookFirst.php
@@ -5,7 +5,7 @@
 namespace Drupal\hook_order_last_alphabetically\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\HookFirst;
+use Drupal\Core\Hook\Attribute\SimpleOrderType;
 
 /**
  * Hook implementations for verifying ordering hooks by attributes.
@@ -22,10 +22,9 @@
 class TestHookFirst {
 
   /**
-   * This pair tests #[HookFirst].
+   * This pair tests OrderType::First.
    */
-  #[HookFirst]
-  #[Hook('custom_hook_test_hook_first')]
+  #[Hook('custom_hook_test_hook_first', order: SimpleOrderType::First)]
   public static function hookFirst(): void {
     $GLOBALS['HookFirst'] = 'HookFirst';
   }
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookLast.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookLast.php
index 864016a538b1..a7738173c2d2 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookLast.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookLast.php
@@ -21,7 +21,7 @@
 class TestHookLast {
 
   /**
-   * This pair tests #[HookLast].
+   * This pair tests OrderType::Last.
    */
   #[Hook('custom_hook_test_hook_last')]
   public static function hookLast(): void {
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderGroup.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderGroup.php
index 607a9d376290..cbf72ddd77b5 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderGroup.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderGroup.php
@@ -21,7 +21,7 @@
 class TestHookOrderGroup {
 
   /**
-   * This pair tests #[HookOrderGroup].
+   * This pair tests OrderType::After with Group.
    */
   #[Hook('custom_hook_extra_types2_alter')]
   public static function customHookExtraTypes(): void {
diff --git a/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php b/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php
index 1752a787e9a6..f4fc3fe81ee0 100644
--- a/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php
+++ b/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php
@@ -5,7 +5,8 @@
 namespace Drupal\hook_second_order_first_alphabetically\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\HookAfter;
+use Drupal\Core\Hook\Attribute\Order;
+use Drupal\Core\Hook\Attribute\OrderType;
 use Drupal\hook_second_order_last_alphabetically\Hook\TestHookAfterClassMethod as TestHookAfterClassMethodForAfter;
 
 /**
@@ -23,10 +24,14 @@
 class TestHookAfterClassMethod {
 
   /**
-   * This pair tests #[HookAfter] with a passed class and method.
+   * This pair tests OrderType::After with a passed class and method.
    */
-  #[HookAfter([[TestHookAfterClassMethodForAfter::class, 'hookAfterClassMethod']])]
-  #[Hook('custom_hook_test_hook_after_class_method')]
+  #[Hook('custom_hook_test_hook_after_class_method',
+    order: new Order(
+      type: OrderType::After,
+      classesAndMethods: [TestHookAfterClassMethodForAfter::class, 'hookAfterClassMethod'],
+    )
+  )]
   public static function hookAfterClassMethod(): void {
     $GLOBALS['HookAfterClassMethod'] = 'HookAfterMethod';
   }
-- 
GitLab


From 8a294534ebab5dc5beda2b9dba7451fafd6b1a79 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Sat, 14 Dec 2024 21:40:11 -0500
Subject: [PATCH 071/173] Code sniffing

---
 core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php              | 2 --
 .../hook_order_first_alphabetically/src/Hook/TestHookLast.php   | 2 +-
 2 files changed, 1 insertion(+), 3 deletions(-)

diff --git a/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php b/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php
index c28145c95896..c0fec656e43c 100644
--- a/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php
+++ b/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php
@@ -2,10 +2,8 @@
 
 namespace Drupal\ckeditor5\Hook;
 
-use Drupal\Core\Hook\Attribute\HookAfter;
 use Drupal\Core\Hook\Attribute\Order;
 use Drupal\Core\Hook\Attribute\OrderType;
-use Drupal\Core\Hook\Attribute\HookOrderGroup;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Asset\AttachedAssetsInterface;
 use Drupal\Core\Render\Element;
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php
index 7655b6bed92f..2f364d8d1dc5 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php
@@ -21,7 +21,7 @@
  */
 class TestHookLast {
 
-    /**
+  /**
    * This pair tests OrderType::Last.
    */
   #[Hook('custom_hook_test_hook_last', order: SimpleOrderType::Last)]
-- 
GitLab


From da620b0c7d3deb09bd0a64e2b0c1c367668b2f4a Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Sat, 14 Dec 2024 21:47:30 -0500
Subject: [PATCH 072/173] Add comments

---
 core/lib/Drupal/Core/Hook/Attribute/Hook.php | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
index 1384b992f5e4..3f54b5f84da9 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -106,6 +106,9 @@ 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 Order|SimpleOrderType|null $order
+   *   (optional) Ordering information if you need to change the current order
+   *   of the implementation.
    */
   public function __construct(
     public string $hook,
@@ -114,6 +117,16 @@ public function __construct(
     public Order|SimpleOrderType|NULL $order = NULL,
   ) {}
 
+  /**
+   * Set necessary parameters for the hook attribute.
+   *
+   * @param string $class
+   *   The class for the hook.
+   * @param string $module
+   *   The module for the hook.
+   * @param string $method
+   *   The method for the hook.
+   */
   public function set(string $class, string $module, string $method): void {
     $this->class = $class;
     if (!$this->module) {
-- 
GitLab


From 5b9c95ef7e4f60944be2513fcb1b6e55b1e4adf2 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Sun, 15 Dec 2024 08:27:08 -0500
Subject: [PATCH 073/173] Refactor ordering

---
 .../Drupal/Core/Extension/ModuleHandler.php   |  8 +--
 core/lib/Drupal/Core/Hook/Attribute/Hook.php  | 16 +++--
 core/lib/Drupal/Core/Hook/Attribute/Order.php | 20 ------
 .../Drupal/Core/Hook/Attribute/OrderType.php  | 12 ----
 .../Core/Hook/Attribute/SimpleOrderType.php   | 12 ----
 core/lib/Drupal/Core/Hook/ComplexOrder.php    | 64 +++++++++++++++++++
 .../Drupal/Core/Hook/HookCollectorPass.php    | 10 +--
 core/lib/Drupal/Core/Hook/HookPriority.php    | 25 +++++---
 core/lib/Drupal/Core/Hook/Order.php           | 22 +++++++
 core/lib/Drupal/Core/Hook/OrderAfter.php      | 17 +++++
 core/lib/Drupal/Core/Hook/OrderBefore.php     | 17 +++++
 .../ckeditor5/src/Hook/Ckeditor5Hooks.php     |  6 +-
 .../src/Hook/TestHookAfter.php                | 10 +--
 .../src/Hook/TestHookBefore.php               |  2 +-
 .../src/Hook/TestHookFirst.php                |  2 +-
 .../src/Hook/TestHookLast.php                 |  6 +-
 .../src/Hook/TestHookOrderGroup.php           |  8 +--
 .../src/Hook/TestHookAfter.php                |  2 +-
 .../src/Hook/TestHookBefore.php               | 10 +--
 .../src/Hook/TestHookFirst.php                |  6 +-
 .../src/Hook/TestHookLast.php                 |  2 +-
 .../src/Hook/TestHookOrderGroup.php           |  2 +-
 .../src/Hook/TestHookAfterClassMethod.php     | 10 ++-
 23 files changed, 184 insertions(+), 105 deletions(-)
 delete mode 100644 core/lib/Drupal/Core/Hook/Attribute/Order.php
 delete mode 100644 core/lib/Drupal/Core/Hook/Attribute/OrderType.php
 delete mode 100644 core/lib/Drupal/Core/Hook/Attribute/SimpleOrderType.php
 create mode 100644 core/lib/Drupal/Core/Hook/ComplexOrder.php
 create mode 100644 core/lib/Drupal/Core/Hook/Order.php
 create mode 100644 core/lib/Drupal/Core/Hook/OrderAfter.php
 create mode 100644 core/lib/Drupal/Core/Hook/OrderBefore.php

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 7c882c308452..0770a12cae5f 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -438,10 +438,10 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
         foreach ($extra_hooks as $extra_hook) {
           $hook_listeners = $this->findListenersForAlter($extra_hook, $hook_listeners, $extra_modules);
         }
-        // Second, gather implementations defined in a
-        // Drupal\Core\Hook\Attribute\HookOrderGroup attribute. These are only
-        // used for ordering because the group might contain hooks not included
-        // in this alter() call.
+        // Second, gather implementations grouped together. These are only used
+        // for ordering because the group might contain hooks not included in
+        // this alter() call. \Drupal\Core\Hook\HookPriority::change()
+        // registers the implementations of a grouped hook.
         foreach (array_merge($extra_hooks, [$type . '_alter']) as $extra_hook) {
           if (isset($this->orderGroups[$extra_hook])) {
             $group = $this->orderGroups[$extra_hook];
diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
index 3f54b5f84da9..2a4cfc0e9095 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -4,12 +4,16 @@
 
 namespace Drupal\Core\Hook\Attribute;
 
+use Drupal\Core\Hook\ComplexOrder;
+use Drupal\Core\Hook\Order;
+
 /**
  * Attribute for defining a class method as a hook implementation.
  *
  * Hook implementations in classes need to be marked with this attribute,
  * using one of the following techniques:
  * - On a method, use this attribute with the hook name:
+ *
  *   @code
  *   #[Hook('user_cancel')]
  *   public function userCancel(...) {}
@@ -90,6 +94,11 @@
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
 class Hook {
 
+  /**
+   * The class the hook implementation is in.
+   *
+   * @var string
+   */
   public string $class = '';
 
   /**
@@ -106,15 +115,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 Order|SimpleOrderType|null $order
-   *   (optional) Ordering information if you need to change the current order
-   *   of the implementation.
+   * @param \Drupal\Core\Hook\Order|\Drupal\Core\Hook\ComplexOrder|null $order
+   *   (optional) Set the order of the implementation.
    */
   public function __construct(
     public string $hook,
     public string $method = '',
     public ?string $module = NULL,
-    public Order|SimpleOrderType|NULL $order = NULL,
+    public Order|ComplexOrder|NULL $order = NULL,
   ) {}
 
   /**
diff --git a/core/lib/Drupal/Core/Hook/Attribute/Order.php b/core/lib/Drupal/Core/Hook/Attribute/Order.php
deleted file mode 100644
index ebd1cd025c69..000000000000
--- a/core/lib/Drupal/Core/Hook/Attribute/Order.php
+++ /dev/null
@@ -1,20 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Core\Hook\Attribute;
-
-class Order {
-
-  public function __construct(
-    public OrderType $type,
-    public array $modules = [],
-    public array $classesAndMethods = [],
-    public array $group = [],
-  ) {
-    if (!$this->modules && !$this->classesAndMethods) {
-      throw new \LogicException('Order must provide elements to order against.');
-    }
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Hook/Attribute/OrderType.php b/core/lib/Drupal/Core/Hook/Attribute/OrderType.php
deleted file mode 100644
index fcb054c1d250..000000000000
--- a/core/lib/Drupal/Core/Hook/Attribute/OrderType.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Core\Hook\Attribute;
-
-enum OrderType: int {
-
-  case Before = 1;
-  case After = 0;
-
-}
diff --git a/core/lib/Drupal/Core/Hook/Attribute/SimpleOrderType.php b/core/lib/Drupal/Core/Hook/Attribute/SimpleOrderType.php
deleted file mode 100644
index 95c2d7352cda..000000000000
--- a/core/lib/Drupal/Core/Hook/Attribute/SimpleOrderType.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Core\Hook\Attribute;
-
-enum SimpleOrderType: int {
-
-  case First = 1;
-  case Last = 0;
-
-}
diff --git a/core/lib/Drupal/Core/Hook/ComplexOrder.php b/core/lib/Drupal/Core/Hook/ComplexOrder.php
new file mode 100644
index 000000000000..30d05a3bde06
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/ComplexOrder.php
@@ -0,0 +1,64 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook;
+
+/**
+ * Set this implementation to be before or after others.
+ */
+abstract readonly class ComplexOrder {
+
+  /**
+   * Whether the priority of this hook should be larger than others.
+   */
+  const bool VALUE = FALSE;
+
+  /**
+   * Whether the priority of this hook should be larger than others.
+   *
+   * The value of this variable is the same as the constant ::VALUE, it only
+   * exists so ComplexOrder and Order types both have the same value property.
+   *
+   * @var bool
+   */
+  public bool $value;
+
+  /**
+   * Constructs a ComplexOrder object.
+   *
+   * @param array $modules
+   *   A list of modules.
+   * @param array $classesAndMethods
+   *   A list of classes and methods, for example:
+   *   @code
+   *     [
+   *       [Foo::class, 'someMethod'],
+   *       [Bar::class, 'someOtherMethod'],
+   *     ]
+   *   @endcode
+   * @param array $group
+   *   A list of hooks to be ordered together. Ordering by attributes happens
+   *   at build time by setting up the order of the listeners of a hook
+   *   correctly. However, ModuleHandlerInterface::alter() can be called with
+   *   multiple hooks runtime. If the hook defined on this method/class
+   *   requires ordering relative to other such hooks then this parameter can
+   *   be used to order relative to implementations of all hooks in the group.
+   *   Include all alter hooks to be ordered against in the group even if no
+   *   single alter() call includes all of them. For example, this can be used
+   *   to order a hook_form_BASE_FORM_ID_alter() implementation relative to
+   *   multiple hook_form_FORM_ID_alter() implementations as
+   *   Drupal\ckeditor5\Hook\Ckeditor5Hooks::formFilterFormatFormAlter() does.
+   */
+  public function __construct(
+    public array $modules = [],
+    public array $classesAndMethods = [],
+    public array $group = [],
+  ) {
+    if (!$this->modules && !$this->classesAndMethods) {
+      throw new \LogicException('Order must provide either modules or class-method pairs to order against.');
+    }
+    $this->value = static::VALUE;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 9d38f355423f..6cfd9129fbce 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -10,7 +10,6 @@
 use Drupal\Core\Extension\ProceduralCall;
 use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\Core\Hook\Attribute\LegacyHook;
-use Drupal\Core\Hook\Attribute\Order;
 use Drupal\Core\Hook\Attribute\StopProceduralHookScan;
 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -85,9 +84,10 @@ public function process(ContainerBuilder $container): array {
             $implementations[$hook->hook][$hook->module][$class][] = $hook->method;
             if ($hook->order) {
               $orderAttributes[] = $hook;
-              if ($hook->order instanceof Order) {
-                foreach ($hook->order->group as $extraHook) {
-                  $orderGroups[$extraHook] = array_merge($orderGroups[$extraHook] ?? [], $hook->order->group);
+              if ($hook->order instanceof ComplexOrder && ($group = $hook->order->group)) {
+                $group[] = $hook->hook;
+                foreach ($group as $extraHook) {
+                  $orderGroups[$extraHook] = array_merge($orderGroups[$extraHook] ?? [], $group);
                 }
               }
             }
@@ -194,7 +194,7 @@ protected static function reOrderServices(ContainerBuilder $container, array $or
       // ::process() adds the hook serving as key to the order group so it
       // does not need to be added if there's a group for the hook.
       $hooks = $orderGroups[$orderAttribute->hook] ?? [$orderAttribute->hook];
-      if ($orderAttribute->order instanceof Order) {
+      if ($orderAttribute->order instanceof ComplexOrder) {
         $others = [];
         foreach ($orderAttribute->order->modules as $modules) {
           foreach ($hooks as $hook) {
diff --git a/core/lib/Drupal/Core/Hook/HookPriority.php b/core/lib/Drupal/Core/Hook/HookPriority.php
index a0b8866c3636..ccdd3ef5657a 100644
--- a/core/lib/Drupal/Core/Hook/HookPriority.php
+++ b/core/lib/Drupal/Core/Hook/HookPriority.php
@@ -5,7 +5,6 @@
 namespace Drupal\Core\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\SimpleOrderType;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 
 /**
@@ -34,11 +33,14 @@ public function __construct(protected ContainerBuilder $container) {}
    * @internal
    */
   public function change(array $hooks, Hook $hook, ?array $others = NULL): void {
-    $class_and_method = "$hook->class::$hook->method";
     if ($others) {
-      $other_specifiers = array_map(fn ($pair) => $pair[0] . '::' . $pair[1], $others);
+      $other_specifiers = array_map(fn ($pair) => is_array($pair) ? $pair[0] . '::' . $pair[1] : throw new \LogicException('classesAndMethods needs to be an array of arrays'), $others);
     }
     if (count($hooks) > 1) {
+      // Mark $hook implementation and everything in $others as implementing
+      // a single combined hook made from $hooks. This is necessary because
+      // ordering is only possible between the implementations of the same
+      // hook.
       $map = $this->container->getParameter('hook_implementations_map');
       $combinedHookTag = implode(':', $hooks);
       $event = "drupal_hook.$combinedHookTag";
@@ -46,9 +48,15 @@ public function change(array $hooks, Hook $hook, ?array $others = NULL): void {
       $data[] = [$hook->class, $hook->method];
       $priority = 0;
       foreach ($data as [$class, $method]) {
-        foreach ($hooks as $hook) {
-          if (isset($map[$hook][$class][$method])) {
-            $map[$combinedHookTag][$class][$method] = $map[$hook][$class][$method];
+        // If the class and method exists at all it surely implements a hook
+        // because it is being ordered against and then this implementation
+        // is already registered in the implementation map and allows finding
+        // out what the corresponding module is. This can't be found out from
+        // parsing the class name because a hook might be implemented on
+        // behalf of another module.
+        foreach ($hooks as $indexHook) {
+          if (isset($map[$indexHook][$class][$method])) {
+            $map[$combinedHookTag][$class][$method] = $map[$indexHook][$class][$method];
             $priority = HookCollectorPass::addTagToDefinition($this->container->findDefinition($class), $event, $method, $priority);
             break;
           }
@@ -70,7 +78,7 @@ public function change(array $hooks, Hook $hook, ?array $others = NULL): void {
           assert(is_int($priority));
           $priorities[$index] = $priority;
           $specifier = "$id::" . $tag['method'];
-          if ($class_and_method === $specifier) {
+          if ($specifier === "$hook->class::$hook->method") {
             $index_this = $index;
           }
           // $others is defined for before and after, for these compare only
@@ -85,7 +93,8 @@ public function change(array $hooks, Hook $hook, ?array $others = NULL): void {
     if (!isset($index_this) || !isset($priorities) || !isset($priorities_other)) {
       return;
     }
-    $shouldBeLarger = boolval($hook->order instanceof SimpleOrderType ? $hook->order->value : $hook->order->type->value);
+
+    $shouldBeLarger = (bool) $hook->order->value;
     // The priority of the hook being changed.
     $priority_this = $priorities[$index_this];
     // The priority of the hook being compared to.
diff --git a/core/lib/Drupal/Core/Hook/Order.php b/core/lib/Drupal/Core/Hook/Order.php
new file mode 100644
index 000000000000..e8c8898d2a03
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Order.php
@@ -0,0 +1,22 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook;
+
+/**
+ * Set this implementation to be first or last.
+ */
+enum Order: int {
+
+  /**
+   * This implementation should fire first.
+   */
+  case First = 1;
+
+  /**
+   * This implementation should fire last.
+   */
+  case Last = 0;
+
+}
diff --git a/core/lib/Drupal/Core/Hook/OrderAfter.php b/core/lib/Drupal/Core/Hook/OrderAfter.php
new file mode 100644
index 000000000000..be7811c1b79c
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/OrderAfter.php
@@ -0,0 +1,17 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook;
+
+/**
+ * Set this implementation to be after others.
+ */
+readonly class OrderAfter extends ComplexOrder {
+
+  /**
+   * After means the priority should not be larger than others.
+   */
+  const bool VALUE = FALSE;
+
+}
diff --git a/core/lib/Drupal/Core/Hook/OrderBefore.php b/core/lib/Drupal/Core/Hook/OrderBefore.php
new file mode 100644
index 000000000000..4b1a1df6208b
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/OrderBefore.php
@@ -0,0 +1,17 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook;
+
+/**
+ * Set this implementation to be before others.
+ */
+readonly class OrderBefore extends ComplexOrder {
+
+  /**
+   * Before means the priority should be larger than others.
+   */
+  const bool VALUE = TRUE;
+
+}
diff --git a/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php b/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php
index c0fec656e43c..1c6128a8c3cd 100644
--- a/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php
+++ b/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php
@@ -2,8 +2,7 @@
 
 namespace Drupal\ckeditor5\Hook;
 
-use Drupal\Core\Hook\Attribute\Order;
-use Drupal\Core\Hook\Attribute\OrderType;
+use Drupal\Core\Hook\OrderAfter;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Asset\AttachedAssetsInterface;
 use Drupal\Core\Render\Element;
@@ -111,8 +110,7 @@ public function theme() : array {
    * CKEditor 5 and will trigger a false error.
    */
   #[Hook('form_filter_format_form_alter',
-    order: new Order(
-      type: OrderType::After,
+    order: new OrderAfter(
       modules: ['editor', 'media'],
       group: ['form_filter_format_add_form_alter', 'form_filter_format_edit_form_alter'],
     )
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php
index 15fbb62434da..58139a01a2e4 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php
@@ -5,8 +5,7 @@
 namespace Drupal\hook_order_first_alphabetically\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\Order;
-use Drupal\Core\Hook\Attribute\OrderType;
+use Drupal\Core\Hook\OrderAfter;
 
 /**
  * Hook implementations for verifying ordering hooks by attributes.
@@ -23,12 +22,9 @@
 class TestHookAfter {
 
   /**
-   * This pair tests OrderType::After.
+   * This pair tests OrderAfter.
    */
-  #[Hook('custom_hook_test_hook_after', order: new Order(
-    type: OrderType::After,
-    modules: ['hook_order_last_alphabetically']
-  ))]
+  #[Hook('custom_hook_test_hook_after', order: new OrderAfter(['hook_order_last_alphabetically']))]
   public static function hookAfter(): void {
     $GLOBALS['HookAfter'] = 'HookAfter';
   }
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookBefore.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookBefore.php
index c83a3a89f0ce..be9fad86510a 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookBefore.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookBefore.php
@@ -21,7 +21,7 @@
 class TestHookBefore {
 
   /**
-   * This pair tests OrderType::Before.
+   * This pair tests OrderBefore.
    */
   #[Hook('custom_hook_test_hook_before')]
   public static function hookBefore(): void {
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookFirst.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookFirst.php
index a1bc165a33ff..003d3ac2a685 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookFirst.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookFirst.php
@@ -21,7 +21,7 @@
 class TestHookFirst {
 
   /**
-   * This pair tests OrderType::First.
+   * This pair tests OrderFirst.
    */
   #[Hook('custom_hook_test_hook_first')]
   public static function hookFirst(): void {
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php
index 2f364d8d1dc5..c7a218995111 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php
@@ -5,7 +5,7 @@
 namespace Drupal\hook_order_first_alphabetically\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\SimpleOrderType;
+use Drupal\Core\Hook\Order;
 
 /**
  * Hook implementations for verifying ordering hooks by attributes.
@@ -22,9 +22,9 @@
 class TestHookLast {
 
   /**
-   * This pair tests OrderType::Last.
+   * This pair tests OrderLast.
    */
-  #[Hook('custom_hook_test_hook_last', order: SimpleOrderType::Last)]
+  #[Hook('custom_hook_test_hook_last', order: Order::Last)]
   public static function hookLast(): void {
     $GLOBALS['HookLast'] = 'HookLast';
   }
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderGroup.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderGroup.php
index bc21ff1a3ea6..2814a5cfa2ed 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderGroup.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderGroup.php
@@ -5,8 +5,7 @@
 namespace Drupal\hook_order_first_alphabetically\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\Order;
-use Drupal\Core\Hook\Attribute\OrderType;
+use Drupal\Core\Hook\OrderAfter;
 
 /**
  * Hook implementations for verifying ordering hooks by attributes.
@@ -23,11 +22,10 @@
 class TestHookOrderGroup {
 
   /**
-   * This pair tests OrderType::After with Group.
+   * This pair tests OrderAfter with Group.
    */
   #[Hook('custom_hook_extra_types1_alter',
-    order: new Order(
-      type: OrderType::After,
+    order: new OrderAfter(
       modules: ['hook_order_last_alphabetically'],
       group: ['custom_hook_extra_types2_alter'],
     )
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookAfter.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookAfter.php
index e15ca55aea6c..87973afcf32c 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookAfter.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookAfter.php
@@ -21,7 +21,7 @@
 class TestHookAfter {
 
   /**
-   * This pair tests OrderType::After.
+   * This pair tests OrderAfter.
    */
   #[Hook('custom_hook_test_hook_after')]
   public static function hookAfter(): void {
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookBefore.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookBefore.php
index 99934423ea48..3ecaea3dc84e 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookBefore.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookBefore.php
@@ -5,8 +5,7 @@
 namespace Drupal\hook_order_last_alphabetically\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\Order;
-use Drupal\Core\Hook\Attribute\OrderType;
+use Drupal\Core\Hook\OrderBefore;
 
 /**
  * Hook implementations for verifying ordering hooks by attributes.
@@ -23,12 +22,9 @@
 class TestHookBefore {
 
   /**
-   * This pair tests OrderType::Before.
+   * This pair tests OrderBefore.
    */
-  #[Hook('custom_hook_test_hook_before', order: new Order(
-    type: OrderType::Before,
-    modules: ['hook_order_first_alphabetically']
-  ))]
+  #[Hook('custom_hook_test_hook_before', order: new OrderBefore(['hook_order_first_alphabetically']))]
   public static function cacheFlush(): void {
     $GLOBALS['HookBefore'] = 'HookBefore';
   }
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookFirst.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookFirst.php
index 06972d5c97d0..0ab7697bef63 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookFirst.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookFirst.php
@@ -5,7 +5,7 @@
 namespace Drupal\hook_order_last_alphabetically\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\SimpleOrderType;
+use Drupal\Core\Hook\Order;
 
 /**
  * Hook implementations for verifying ordering hooks by attributes.
@@ -22,9 +22,9 @@
 class TestHookFirst {
 
   /**
-   * This pair tests OrderType::First.
+   * This pair tests OrderFirst.
    */
-  #[Hook('custom_hook_test_hook_first', order: SimpleOrderType::First)]
+  #[Hook('custom_hook_test_hook_first', order: Order::First)]
   public static function hookFirst(): void {
     $GLOBALS['HookFirst'] = 'HookFirst';
   }
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookLast.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookLast.php
index a7738173c2d2..2999ee66a1ca 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookLast.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookLast.php
@@ -21,7 +21,7 @@
 class TestHookLast {
 
   /**
-   * This pair tests OrderType::Last.
+   * This pair tests OrderLast.
    */
   #[Hook('custom_hook_test_hook_last')]
   public static function hookLast(): void {
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderGroup.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderGroup.php
index cbf72ddd77b5..3840fd26061b 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderGroup.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderGroup.php
@@ -21,7 +21,7 @@
 class TestHookOrderGroup {
 
   /**
-   * This pair tests OrderType::After with Group.
+   * This pair tests OrderAfter with Group.
    */
   #[Hook('custom_hook_extra_types2_alter')]
   public static function customHookExtraTypes(): void {
diff --git a/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php b/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php
index f4fc3fe81ee0..5a50f166efac 100644
--- a/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php
+++ b/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php
@@ -5,8 +5,7 @@
 namespace Drupal\hook_second_order_first_alphabetically\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\Order;
-use Drupal\Core\Hook\Attribute\OrderType;
+use Drupal\Core\Hook\OrderAfter;
 use Drupal\hook_second_order_last_alphabetically\Hook\TestHookAfterClassMethod as TestHookAfterClassMethodForAfter;
 
 /**
@@ -24,12 +23,11 @@
 class TestHookAfterClassMethod {
 
   /**
-   * This pair tests OrderType::After with a passed class and method.
+   * This pair tests OrderAfter with a passed class and method.
    */
   #[Hook('custom_hook_test_hook_after_class_method',
-    order: new Order(
-      type: OrderType::After,
-      classesAndMethods: [TestHookAfterClassMethodForAfter::class, 'hookAfterClassMethod'],
+    order: new OrderAfter(
+      classesAndMethods: [[TestHookAfterClassMethodForAfter::class, 'hookAfterClassMethod']],
     )
   )]
   public static function hookAfterClassMethod(): void {
-- 
GitLab


From 63c9657f3f169bb83607293143af9d0b716f5b20 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Sun, 15 Dec 2024 09:44:59 -0500
Subject: [PATCH 074/173] Comments and rename method and variable for clarity

---
 core/lib/Drupal/Core/Extension/ModuleHandler.php   |  4 ++--
 core/lib/Drupal/Core/Hook/HookCollectorPass.php    | 14 +++++++-------
 core/lib/Drupal/Core/Hook/HookPriority.php         |  3 +--
 .../src/Hook/TestHookAfter.php                     |  2 +-
 .../src/Hook/TestHookBefore.php                    |  2 +-
 .../src/Hook/TestHookFirst.php                     |  2 +-
 .../src/Hook/TestHookLast.php                      |  2 +-
 .../src/Hook/TestHookOrderGroup.php                |  2 +-
 .../src/Hook/TestHookAfter.php                     |  2 +-
 .../src/Hook/TestHookBefore.php                    |  2 +-
 .../src/Hook/TestHookFirst.php                     |  2 +-
 .../src/Hook/TestHookLast.php                      |  2 +-
 .../src/Hook/TestHookOrderGroup.php                |  2 +-
 .../src/Hook/TestHookAfterClassMethod.php          |  2 +-
 .../src/Hook/TestHookAfterClassMethod.php          |  2 +-
 15 files changed, 22 insertions(+), 23 deletions(-)

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 0770a12cae5f..1d1ae48093ca 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -447,13 +447,13 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
             $group = $this->orderGroups[$extra_hook];
             $extra_listeners = $this->findListenersForAlter(implode(':', $group));
             // Remove already ordered hooks.
-            $extra_types = array_diff($extra_hooks, $group);
+            $extra_hooks = array_diff($extra_hooks, $group);
           }
         }
       }
       // If multiple alters were called, but they were already ordered by
       // ordering attributes then keep that order.
-      if (isset($extra_types) && empty($extra_types)) {
+      if (isset($extra_hooks) && empty($extra_hooks)) {
         $modules = array_keys(array_intersect_key($extra_listeners, $hook_listeners));
       }
       else {
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 6cfd9129fbce..e6dbc90b59f5 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -59,7 +59,7 @@ class HookCollectorPass implements CompilerPassInterface {
   /**
    * A list of attributes for hook implementations.
    *
-   * Keys are module, class and method. values are Hook attributes.
+   * Keys are module, class and method. Values are Hook attributes.
    */
   protected array $moduleHooks = [];
 
@@ -101,8 +101,8 @@ public function process(ContainerBuilder $container): array {
     // is removed.
     // @see https://www.drupal.org/project/drupal/issues/3481778
     if (count($container->getDefinitions()) > 1) {
-      static::registerServices($container, $collector, $implementations, $legacyImplementations ?? [], $orderGroups);
-      static::reOrderServices($container, $orderAttributes, $orderGroups, $implementations);
+      static::registerImplementations($container, $collector, $implementations, $legacyImplementations ?? [], $orderGroups);
+      static::reOrderImplementations($container, $orderAttributes, $orderGroups, $implementations);
     }
     return $implementations;
   }
@@ -126,7 +126,7 @@ public function process(ContainerBuilder $container): array {
    *
    * @return void
    */
-  protected static function registerServices(ContainerBuilder $container, HookCollectorPass $collector, array $implementations, array $legacyImplementations, array $orderGroups): void {
+  protected static function registerImplementations(ContainerBuilder $container, HookCollectorPass $collector, array $implementations, array $legacyImplementations, array $orderGroups): void {
     $container->register(ProceduralCall::class, ProceduralCall::class)
       ->addArgument($collector->includes);
     $groupIncludes = [];
@@ -174,7 +174,7 @@ protected static function registerServices(ContainerBuilder $container, HookColl
   }
 
   /**
-   * Reorder services that have attributes specifying an order.
+   * Reorder hook implementations specifying an order.
    *
    * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
    *   The container.
@@ -187,7 +187,7 @@ protected static function registerServices(ContainerBuilder $container, HookColl
    *
    * @return void
    */
-  protected static function reOrderServices(ContainerBuilder $container, array $orderAttributes, array $orderGroups, array $implementations): void {
+  protected static function reOrderImplementations(ContainerBuilder $container, array $orderAttributes, array $orderGroups, array $implementations): void {
     $hookPriority = new HookPriority($container);
     foreach ($orderAttributes as $orderAttribute) {
       assert($orderAttribute instanceof Hook);
@@ -442,7 +442,7 @@ public static function checkForProceduralOnlyHooks(Hook $hook, string $class = '
    *   A list of class and method reflections.
    *
    * @return array
-   *   A list of Hook|HookOrderInterface|HookOrderGroup attribute instances.
+   *   A list of Hook attribute instances.
    */
   protected static function getAttributeInstances(array $attributes, array $reflections): array {
     foreach ($reflections as $reflection) {
diff --git a/core/lib/Drupal/Core/Hook/HookPriority.php b/core/lib/Drupal/Core/Hook/HookPriority.php
index ccdd3ef5657a..603dbaa6f379 100644
--- a/core/lib/Drupal/Core/Hook/HookPriority.php
+++ b/core/lib/Drupal/Core/Hook/HookPriority.php
@@ -22,8 +22,7 @@ public function __construct(protected ContainerBuilder $container) {}
    * @param array $hooks
    *   The list of hooks to order. The list always contains the hook defined
    *   in Drupal\Core\Hook\Attribute, and it might also contain
-   *   the hooks listed in the Drupal\Core\Hook\Attribute\HookOrderGroup
-   *   attribute.
+   *   the hooks listed in the Drupal\Core\Hook\ComplexOrder $group
    * @param \Drupal\Core\Hook\Attribute\Hook $hook
    *   The hook attribute.
    * @param array|null $others
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php
index 58139a01a2e4..9dd41b0cbf06 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookAfter.php
@@ -17,7 +17,7 @@
  * In the normal order a hook implemented by first alphabetically would run
  * before the same hook in last alphabetically.
  *
- * Each method pair tests one hook ordering attribute.
+ * Each method pair tests one hook ordering permutation.
  */
 class TestHookAfter {
 
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookBefore.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookBefore.php
index be9fad86510a..3b7ce8df15ac 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookBefore.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookBefore.php
@@ -16,7 +16,7 @@
  * In the normal order a hook implemented by first alphabetically would run
  * before the same hook in last alphabetically.
  *
- * Each method pair tests one hook ordering attribute.
+ * Each method pair tests one hook ordering permutation.
  */
 class TestHookBefore {
 
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookFirst.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookFirst.php
index 003d3ac2a685..31841f75549a 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookFirst.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookFirst.php
@@ -16,7 +16,7 @@
  * In the normal order a hook implemented by first alphabetically would run
  * before the same hook in last alphabetically.
  *
- * Each method pair tests one hook ordering attribute.
+ * Each method pair tests one hook ordering permutation.
  */
 class TestHookFirst {
 
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php
index c7a218995111..b93fd5155a62 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookLast.php
@@ -17,7 +17,7 @@
  * In the normal order a hook implemented by first alphabetically would run
  * before the same hook in last alphabetically.
  *
- * Each method pair tests one hook ordering attribute.
+ * Each method pair tests one hook ordering permutation.
  */
 class TestHookLast {
 
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderGroup.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderGroup.php
index 2814a5cfa2ed..87b441159a3d 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderGroup.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderGroup.php
@@ -17,7 +17,7 @@
  * In the normal order a hook implemented by first alphabetically would run
  * before the same hook in last alphabetically.
  *
- * Each method pair tests one hook ordering attribute.
+ * Each method pair tests one hook ordering permutation.
  */
 class TestHookOrderGroup {
 
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookAfter.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookAfter.php
index 87973afcf32c..c132f940c319 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookAfter.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookAfter.php
@@ -16,7 +16,7 @@
  * In the normal order a hook implemented by first alphabetically would run
  * before the same hook in last alphabetically.
  *
- * Each method pair tests one hook ordering attribute.
+ * Each method pair tests one hook ordering permutation.
  */
 class TestHookAfter {
 
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookBefore.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookBefore.php
index 3ecaea3dc84e..03b19b10ffe9 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookBefore.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookBefore.php
@@ -17,7 +17,7 @@
  * In the normal order a hook implemented by first alphabetically would run
  * before the same hook in last alphabetically.
  *
- * Each method pair tests one hook ordering attribute.
+ * Each method pair tests one hook ordering permutation.
  */
 class TestHookBefore {
 
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookFirst.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookFirst.php
index 0ab7697bef63..ee659a32585f 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookFirst.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookFirst.php
@@ -17,7 +17,7 @@
  * In the normal order a hook implemented by first alphabetically would run
  * before the same hook in last alphabetically.
  *
- * Each method pair tests one hook ordering attribute.
+ * Each method pair tests one hook ordering permutation.
  */
 class TestHookFirst {
 
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookLast.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookLast.php
index 2999ee66a1ca..36b32f08678b 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookLast.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookLast.php
@@ -16,7 +16,7 @@
  * In the normal order a hook implemented by first alphabetically would run
  * before the same hook in last alphabetically.
  *
- * Each method pair tests one hook ordering attribute.
+ * Each method pair tests one hook ordering permutation.
  */
 class TestHookLast {
 
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderGroup.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderGroup.php
index 3840fd26061b..faac9868ce90 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderGroup.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderGroup.php
@@ -16,7 +16,7 @@
  * In the normal order a hook implemented by first alphabetically would run
  * before the same hook in last alphabetically.
  *
- * Each method pair tests one hook ordering attribute.
+ * Each method pair tests one hook ordering permutation.
  */
 class TestHookOrderGroup {
 
diff --git a/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php b/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php
index 5a50f166efac..b37339173578 100644
--- a/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php
+++ b/core/modules/system/tests/modules/hook_second_order_first_alphabetically/src/Hook/TestHookAfterClassMethod.php
@@ -18,7 +18,7 @@
  * In the normal order a hook implemented by first alphabetically would run
  * before the same hook in last alphabetically.
  *
- * Each method pair tests one hook ordering attribute.
+ * Each method pair tests one hook ordering permutation.
  */
 class TestHookAfterClassMethod {
 
diff --git a/core/modules/system/tests/modules/hook_second_order_last_alphabetically/src/Hook/TestHookAfterClassMethod.php b/core/modules/system/tests/modules/hook_second_order_last_alphabetically/src/Hook/TestHookAfterClassMethod.php
index dc3e6db8d2c3..d69aaa546fb0 100644
--- a/core/modules/system/tests/modules/hook_second_order_last_alphabetically/src/Hook/TestHookAfterClassMethod.php
+++ b/core/modules/system/tests/modules/hook_second_order_last_alphabetically/src/Hook/TestHookAfterClassMethod.php
@@ -16,7 +16,7 @@
  * In the normal order a hook implemented by first alphabetically would run
  * before the same hook in last alphabetically.
  *
- * Each method pair tests one hook ordering attribute.
+ * Each method pair tests one hook ordering permutation.
  */
 class TestHookAfterClassMethod {
 
-- 
GitLab


From 60dcb4b44aa70214f77ddf7ef740be1ed0e21c6f Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Mon, 16 Dec 2024 09:21:11 -0500
Subject: [PATCH 075/173] Move complex hook registration to HCP

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 73 +++++++++++++++----
 core/lib/Drupal/Core/Hook/HookPriority.php    | 52 ++-----------
 2 files changed, 68 insertions(+), 57 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index e6dbc90b59f5..5c9b25697ec1 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -71,6 +71,7 @@ public function process(ContainerBuilder $container): array {
     $implementations = [];
     $orderGroups = [];
     $orderAttributes = [];
+    $moduleFinder = [];
     foreach (array_keys($container->getParameter('container.modules')) as $module) {
       foreach ($collector->moduleHooks[$module] ?? [] as $class => $methods) {
         foreach ($methods as $method => $hooks) {
@@ -82,6 +83,7 @@ public function process(ContainerBuilder $container): array {
             }
             $legacyImplementations[$hook->hook][$hook->module] = '';
             $implementations[$hook->hook][$hook->module][$class][] = $hook->method;
+            $moduleFinder[$class][$method] = $hook->module;
             if ($hook->order) {
               $orderAttributes[] = $hook;
               if ($hook->order instanceof ComplexOrder && ($group = $hook->order->group)) {
@@ -102,7 +104,7 @@ public function process(ContainerBuilder $container): array {
     // @see https://www.drupal.org/project/drupal/issues/3481778
     if (count($container->getDefinitions()) > 1) {
       static::registerImplementations($container, $collector, $implementations, $legacyImplementations ?? [], $orderGroups);
-      static::reOrderImplementations($container, $orderAttributes, $orderGroups, $implementations);
+      static::reOrderImplementations($container, $orderAttributes, $orderGroups, $implementations, $moduleFinder);
     }
     return $implementations;
   }
@@ -160,7 +162,7 @@ protected static function registerImplementations(ContainerBuilder $container, H
           }
           foreach ($method_hooks as $method) {
             $map[$hook][$class][$method] = $module;
-            $priority = self::addTagToDefinition($definition, "drupal_hook.$hook", $method, $priority);
+            $priority = self::addTagToDefinition($definition, $hook, $method, $priority);
           }
         }
         unset($implementations[$hook][$module]);
@@ -184,33 +186,53 @@ protected static function registerImplementations(ContainerBuilder $container, H
    *   Groups to order by.
    * @param array $implementations
    *   Hook implementations.
+   * @param array $moduleFinder
+   *   An array keyed by the class and method of a hook implementation, value
+   *   is the module. This is not necessarily the same as the module the class
+   *   is in because the implementation might be on behalf of another module.
    *
    * @return void
    */
-  protected static function reOrderImplementations(ContainerBuilder $container, array $orderAttributes, array $orderGroups, array $implementations): void {
+  protected static function reOrderImplementations(ContainerBuilder $container, array $orderAttributes, array $orderGroups, array $implementations, array $moduleFinder): void {
     $hookPriority = new HookPriority($container);
     foreach ($orderAttributes as $orderAttribute) {
       assert($orderAttribute instanceof Hook);
       // ::process() adds the hook serving as key to the order group so it
       // does not need to be added if there's a group for the hook.
       $hooks = $orderGroups[$orderAttribute->hook] ?? [$orderAttribute->hook];
+      $combinedHook = implode(':', $hooks);
       if ($orderAttribute->order instanceof ComplexOrder) {
-        $others = [];
+        // Verify the correct structure of
+        // $orderAttribute->order->classesAndMethods and create specifiers
+        // for HookPriority::change() while at it.
+        $otherSpecifiers = array_map(fn ($pair) => is_array($pair) ? $pair[0] . '::' . $pair[1] : throw new \LogicException('classesAndMethods needs to be an array of arrays'), $orderAttribute->order->classesAndMethods);
+        // Collect classes and methods for
+        // self::registerComplexHookImplementations().
+        $classesAndMethods = $orderAttribute->order->classesAndMethods;
         foreach ($orderAttribute->order->modules as $modules) {
           foreach ($hooks as $hook) {
             foreach ($implementations[$hook][$modules] ?? [] as $class => $methods) {
               foreach ($methods as $method) {
-                $others[] = [$class, $method];
+                $classesAndMethods[] = [$class, $method];
+                $otherSpecifiers[] = "$class::$method";
               }
             }
           }
         }
-        $others = array_merge($others, $orderAttribute->order->classesAndMethods);
+        if (count($hooks) > 1) {
+          // The hook implementation in $orderAttribute and everything in
+          // $classesAndMethods will be ordered relative to each other as if
+          // they were implementing a single hook. This needs to be marked on
+          // their service definition and added to the
+          // hook_implementations_map container parameter.
+          $classesAndMethods[] = [$orderAttribute->class, $orderAttribute->method];
+          self::registerComplexHookImplementations($container, $classesAndMethods, $moduleFinder, $combinedHook);
+        }
       }
       else {
-        $others = NULL;
+        $otherSpecifiers = NULL;
       }
-      $hookPriority->change($hooks, $orderAttribute, $others);
+      $hookPriority->change("drupal_hook.$combinedHook", $orderAttribute, $otherSpecifiers);
     }
   }
 
@@ -220,7 +242,7 @@ protected static function reOrderImplementations(ContainerBuilder $container, ar
    * @param array $module_filenames
    *   An associative array. Keys are the module names, values are relevant
    *   info yml file path.
-   * @param Symfony\Component\DependencyInjection\ContainerBuilder|null $container
+   * @param \Symfony\Component\DependencyInjection\ContainerBuilder|null $container
    *   The container.
    *
    * @return static
@@ -459,8 +481,8 @@ protected static function getAttributeInstances(array $attributes, array $reflec
    *
    * @param \Symfony\Component\DependencyInjection\Definition $definition
    *   The service definition.
-   * @param string $event
-   *   The name of the event, typically starts with drupal_hook.
+   * @param string $hook
+   *   The name of the hook.
    * @param string $method
    *   The method.
    * @param int $priority
@@ -469,13 +491,38 @@ protected static function getAttributeInstances(array $attributes, array $reflec
    * @return int
    *   A new priority, guaranteed to be lower than $priority.
    */
-  public static function addTagToDefinition(Definition $definition, string $event, string $method, int $priority): int {
+  protected static function addTagToDefinition(Definition $definition, string $hook, string $method, int $priority): int {
     $definition->addTag('kernel.event_listener', [
-      'event' => $event,
+      'event' => "drupal_hook.$hook",
       'method' => $method,
       'priority' => $priority--,
     ]);
     return $priority;
   }
 
+  /**
+   * Register complex hook implementations.
+   *
+   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
+   *   The container.
+   * @param array $classesAndMethods
+   *   A list of class-and-method pairs.
+   * @param array $moduleFinder
+   *   A module finder array, see ::reOrderImplementations() for explanation.
+   * @param string $combinedHook
+   *   A string made form list of hooks separated by :
+   */
+  protected static function registerComplexHookImplementations(ContainerBuilder $container, array $classesAndMethods, array $moduleFinder, string $combinedHook): void {
+    $map = $container->getParameter('hook_implementations_map');
+    $priority = 0;
+    foreach ($classesAndMethods as [$class, $method]) {
+      // Ordering against not installed modules is possible.
+      if (isset($moduleFinder[$class][$method])) {
+        $map[$combinedHook][$class][$method] = $moduleFinder[$class][$method];
+        $priority = self::addTagToDefinition($container->findDefinition($class), $combinedHook, $method, $priority);
+      }
+    }
+    $container->setParameter('hook_implementations_map', $map);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Hook/HookPriority.php b/core/lib/Drupal/Core/Hook/HookPriority.php
index 603dbaa6f379..72d931963d47 100644
--- a/core/lib/Drupal/Core/Hook/HookPriority.php
+++ b/core/lib/Drupal/Core/Hook/HookPriority.php
@@ -19,53 +19,17 @@ public function __construct(protected ContainerBuilder $container) {}
   /**
    * Change the priority of a hook implementation.
    *
-   * @param array $hooks
-   *   The list of hooks to order. The list always contains the hook defined
-   *   in Drupal\Core\Hook\Attribute, and it might also contain
-   *   the hooks listed in the Drupal\Core\Hook\ComplexOrder $group
+   * @param string $event
+   *   Listeners to this event will be ordered.
    * @param \Drupal\Core\Hook\Attribute\Hook $hook
    *   The hook attribute.
-   * @param array|null $others
+   * @param array|null $other_specifiers
    *   Other hook implementations to compare to, if any. The array is a list of
-   *   class and method pairs.
+   *   strings, each string is a class and method separated by ::.
    *
    * @internal
    */
-  public function change(array $hooks, Hook $hook, ?array $others = NULL): void {
-    if ($others) {
-      $other_specifiers = array_map(fn ($pair) => is_array($pair) ? $pair[0] . '::' . $pair[1] : throw new \LogicException('classesAndMethods needs to be an array of arrays'), $others);
-    }
-    if (count($hooks) > 1) {
-      // Mark $hook implementation and everything in $others as implementing
-      // a single combined hook made from $hooks. This is necessary because
-      // ordering is only possible between the implementations of the same
-      // hook.
-      $map = $this->container->getParameter('hook_implementations_map');
-      $combinedHookTag = implode(':', $hooks);
-      $event = "drupal_hook.$combinedHookTag";
-      $data = $others;
-      $data[] = [$hook->class, $hook->method];
-      $priority = 0;
-      foreach ($data as [$class, $method]) {
-        // If the class and method exists at all it surely implements a hook
-        // because it is being ordered against and then this implementation
-        // is already registered in the implementation map and allows finding
-        // out what the corresponding module is. This can't be found out from
-        // parsing the class name because a hook might be implemented on
-        // behalf of another module.
-        foreach ($hooks as $indexHook) {
-          if (isset($map[$indexHook][$class][$method])) {
-            $map[$combinedHookTag][$class][$method] = $map[$indexHook][$class][$method];
-            $priority = HookCollectorPass::addTagToDefinition($this->container->findDefinition($class), $event, $method, $priority);
-            break;
-          }
-        }
-      }
-      $this->container->setParameter('hook_implementations_map', $map);
-    }
-    else {
-      $event = 'drupal_hook.' . reset($hooks);
-    }
+  public function change(string $event, Hook $hook, ?array $other_specifiers = NULL): void {
     foreach ($this->container->findTaggedServiceIds('kernel.event_listener') as $id => $tags) {
       foreach ($tags as $key => $tag) {
         if ($tag['event'] === $event) {
@@ -80,9 +44,9 @@ public function change(array $hooks, Hook $hook, ?array $others = NULL): void {
           if ($specifier === "$hook->class::$hook->method") {
             $index_this = $index;
           }
-          // $others is defined for before and after, for these compare only
-          // the priority of those. For first and last the priority of every
-          // other hook matters.
+          // $other_specifiers is defined for before and after, for these
+          // compare only the priority of those. For first and last the
+          // priority of every other hook matters.
           elseif (!isset($other_specifiers) || in_array($specifier, $other_specifiers)) {
             $priorities_other[$specifier] = $priority;
           }
-- 
GitLab


From e05f9f0e8596fa9b29b1d79aec61553ad6681bf3 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Mon, 16 Dec 2024 10:53:56 -0500
Subject: [PATCH 076/173] Type fixing

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 5c9b25697ec1..4b45bf841141 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -481,8 +481,8 @@ protected static function getAttributeInstances(array $attributes, array $reflec
    *
    * @param \Symfony\Component\DependencyInjection\Definition $definition
    *   The service definition.
-   * @param string $hook
-   *   The name of the hook.
+   * @param string|int $hook
+   *   The name of the hook
    * @param string $method
    *   The method.
    * @param int $priority
@@ -491,7 +491,7 @@ protected static function getAttributeInstances(array $attributes, array $reflec
    * @return int
    *   A new priority, guaranteed to be lower than $priority.
    */
-  protected static function addTagToDefinition(Definition $definition, string $hook, string $method, int $priority): int {
+  protected static function addTagToDefinition(Definition $definition, string|int $hook, string $method, int $priority): int {
     $definition->addTag('kernel.event_listener', [
       'event' => "drupal_hook.$hook",
       'method' => $method,
-- 
GitLab


From a006ab89d017eebc3bceaecd9c558c67b29f906a Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 31 Dec 2024 09:37:21 -0500
Subject: [PATCH 077/173] Convert content_translation

---
 .../content_translation.module                | 22 -------------------
 .../src/Hook/ContentTranslationHooks.php      |  5 +++--
 2 files changed, 3 insertions(+), 24 deletions(-)

diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index fd038312f285..ee1ffac87cba 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 0f38a7f005db..f878db7feb0b 100644
--- a/core/modules/content_translation/src/Hook/ContentTranslationHooks.php
+++ b/core/modules/content_translation/src/Hook/ContentTranslationHooks.php
@@ -17,6 +17,7 @@
 use Drupal\Core\Url;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Order;
 
 /**
  * Hook implementations for content_translation.
@@ -139,7 +140,7 @@ public function languageTypesInfoAlter(array &$language_types): void {
    *
    * @see \Drupal\Core\Entity\Annotation\EntityType
    */
-  #[Hook('entity_type_alter')]
+  #[Hook('entity_type_alter', order: Order::First)]
   public function entityTypeAlter(array &$entity_types) : void {
     // Provide defaults for translation info.
     /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
@@ -189,7 +190,7 @@ public function entityTypeAlter(array &$entity_types) : void {
    * @see content_translation_entity_bundle_info_alter()
    * @see \Drupal\content_translation\ContentTranslationManager::isEnabled()
    */
-  #[Hook('language_content_settings_insert')]
+  #[Hook('language_content_settings_insert', order: Order::Last)]
   public function languageContentSettingsInsert(ContentLanguageSettingsInterface $settings): void {
     if ($settings->getThirdPartySetting('content_translation', 'enabled', FALSE)) {
       _content_translation_install_field_storage_definitions($settings->getTargetEntityTypeId());
-- 
GitLab


From a69a8e87335d9eff6b5b2f726f08d54e7635e08c Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 31 Dec 2024 09:39:03 -0500
Subject: [PATCH 078/173] Convert layout_builder

---
 core/modules/layout_builder/layout_builder.module  | 14 --------------
 .../layout_builder/src/Hook/LayoutBuilderHooks.php |  3 ++-
 2 files changed, 2 insertions(+), 15 deletions(-)

diff --git a/core/modules/layout_builder/layout_builder.module b/core/modules/layout_builder/layout_builder.module
index 32ce8bee9459..37dc492bf787 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 d12f9f0bc684..5be94e5e80ee 100644
--- a/core/modules/layout_builder/src/Hook/LayoutBuilderHooks.php
+++ b/core/modules/layout_builder/src/Hook/LayoutBuilderHooks.php
@@ -26,6 +26,7 @@
 use Drupal\Core\Url;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Order;
 
 /**
  * Hook implementations for layout_builder.
@@ -151,7 +152,7 @@ public function fieldConfigDelete(FieldConfigInterface $field_config): void {
    * @see \Drupal\layout_builder\Plugin\Block\ExtraFieldBlock::build()
    * @see layout_builder_module_implements_alter()
    */
-  #[Hook('entity_view_alter')]
+  #[Hook('entity_view_alter', order: Order::Last)]
   public function entityViewAlter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display): void {
     // Only replace extra fields when Layout Builder has been used to alter the
     // build. See \Drupal\layout_builder\Entity\LayoutBuilderEntityViewDisplay::buildMultiple().
-- 
GitLab


From 7943fc38a0087d636a5087feb0b3c26e07129174 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 31 Dec 2024 09:42:48 -0500
Subject: [PATCH 079/173] layout_builder_test

---
 .../layout_builder_test/layout_builder_test.module | 14 --------------
 .../src/Hook/LayoutBuilderTestHooks.php            | 11 ++++++++++-
 2 files changed, 10 insertions(+), 15 deletions(-)

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 de2e5316d073..d7dda399be39 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 7a0d47979035..d968b97c1e22 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,8 @@
 use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\OrderBefore;
+use Drupal\layout_builder\Hook\LayoutBuilderHooks;
 
 /**
  * Hook implementations for layout_builder_test.
@@ -115,7 +117,14 @@ public function layoutBuilderEntityFormDisplayAlter(EntityFormDisplayInterface $
   /**
    * Implements hook_system_breadcrumb_alter().
    */
-  #[Hook('system_breadcrumb_alter')]
+  #[Hook(
+    'system_breadcrumb_alter',
+    order: new OrderBefore(
+      classesAndMethods: [
+        [LayoutBuilderHooks::class, 'systemBreadcrumbAlter'],
+      ]
+    )
+  )]
   public function systemBreadcrumbAlter(Breadcrumb &$breadcrumb, RouteMatchInterface $route_match, array $context): void {
     $breadcrumb->addLink(Link::fromTextAndUrl('External link', Url::fromUri('http://www.example.com')));
   }
-- 
GitLab


From 4ea192181ce19d06ee21e455a423545fc8d739f9 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 31 Dec 2024 09:46:48 -0500
Subject: [PATCH 080/173] convert part of navigation

---
 core/modules/navigation/navigation.module            | 5 -----
 core/modules/navigation/src/Hook/NavigationHooks.php | 3 ++-
 2 files changed, 2 insertions(+), 6 deletions(-)

diff --git a/core/modules/navigation/navigation.module b/core/modules/navigation/navigation.module
index dbc11f191e64..ecf6a31f934f 100644
--- a/core/modules/navigation/navigation.module
+++ b/core/modules/navigation/navigation.module
@@ -10,11 +10,6 @@
  * 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']);
diff --git a/core/modules/navigation/src/Hook/NavigationHooks.php b/core/modules/navigation/src/Hook/NavigationHooks.php
index 11bf1773dfe3..30c76860885d 100644
--- a/core/modules/navigation/src/Hook/NavigationHooks.php
+++ b/core/modules/navigation/src/Hook/NavigationHooks.php
@@ -7,6 +7,7 @@
 use Drupal\Core\Config\Action\ConfigActionManager;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Order;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
@@ -79,7 +80,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;
-- 
GitLab


From 69afccdf0c46ab46ea8ae979ab609df836d219af Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 31 Dec 2024 10:15:03 -0500
Subject: [PATCH 081/173] Address comments in MR

---
 core/lib/Drupal/Core/Extension/ModuleHandler.php | 5 ++++-
 core/lib/Drupal/Core/Hook/ComplexOrder.php       | 4 ++--
 core/lib/Drupal/Core/Hook/HookCollectorPass.php  | 5 ++---
 3 files changed, 8 insertions(+), 6 deletions(-)

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 1d1ae48093ca..890deaf10451 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -81,7 +81,10 @@ class ModuleHandler implements ModuleHandlerInterface {
    * @param array $groupIncludes
    *   An array of .inc files to get helpers from.
    * @param array $orderGroups
-   *   An array of hooks that have been ordered by attributes.
+   *   A multidimensional array of hooks that have been ordered and the group
+   *   of hooks they have been ordered against. This is stored separately from
+   *   $hookImplementationsMap to prevent ordering again since this group has
+   *   already been fully ordered in HookCollectorPass.
    *
    * @see \Drupal\Core\DrupalKernel
    * @see \Drupal\Core\CoreServiceProvider
diff --git a/core/lib/Drupal/Core/Hook/ComplexOrder.php b/core/lib/Drupal/Core/Hook/ComplexOrder.php
index 30d05a3bde06..2d9ae3839306 100644
--- a/core/lib/Drupal/Core/Hook/ComplexOrder.php
+++ b/core/lib/Drupal/Core/Hook/ComplexOrder.php
@@ -17,8 +17,8 @@
   /**
    * Whether the priority of this hook should be larger than others.
    *
-   * The value of this variable is the same as the constant ::VALUE, it only
-   * exists so ComplexOrder and Order types both have the same value property.
+   * This is fixed to the constant ::VALUE, it simplifies ordering by ensuring
+   * ComplexOrder and Order types both have a value property.
    *
    * @var bool
    */
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 4b45bf841141..04c1ca26473c 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -112,8 +112,7 @@ public function process(ContainerBuilder $container): array {
   /**
    * Register hook implementations as event listeners.
    *
-   * Passes required include information to module_handler.
-   * Passes required runtime ordering information to module_handler.
+   * Passes required include and ordering information to module_handler.
    *
    * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
    *   The container.
@@ -122,7 +121,7 @@ public function process(ContainerBuilder $container): array {
    * @param array $implementations
    *   All implementations.
    * @param array $legacyImplementations
-   *   Modules that implement hooks.
+   *   Modules that implement legacy hooks.
    * @param array $orderGroups
    *   Groups of hooks to reorder.
    *
-- 
GitLab


From f015a14305ffbe053d5d1e6e7dab6f13041704c2 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 31 Dec 2024 10:49:09 -0500
Subject: [PATCH 082/173] Update correct hooks

---
 .../src/Hook/ContentTranslationHooks.php                 | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/core/modules/content_translation/src/Hook/ContentTranslationHooks.php b/core/modules/content_translation/src/Hook/ContentTranslationHooks.php
index f878db7feb0b..8c0d0e816f61 100644
--- a/core/modules/content_translation/src/Hook/ContentTranslationHooks.php
+++ b/core/modules/content_translation/src/Hook/ContentTranslationHooks.php
@@ -140,7 +140,7 @@ public function languageTypesInfoAlter(array &$language_types): void {
    *
    * @see \Drupal\Core\Entity\Annotation\EntityType
    */
-  #[Hook('entity_type_alter', order: Order::First)]
+  #[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 */
@@ -190,8 +190,13 @@ public function entityTypeAlter(array &$entity_types) : void {
    * @see content_translation_entity_bundle_info_alter()
    * @see \Drupal\content_translation\ContentTranslationManager::isEnabled()
    */
+<<<<<<< HEAD
   #[Hook('language_content_settings_insert', order: Order::Last)]
   public function languageContentSettingsInsert(ContentLanguageSettingsInterface $settings): void {
+=======
+  #[Hook('language_content_settings_insert')]
+  public function languageContentSettingsInsert(ContentLanguageSettingsInterface $settings): void {
+>>>>>>> beef39243fd (Update correct hooks)
     if ($settings->getThirdPartySetting('content_translation', 'enabled', FALSE)) {
       _content_translation_install_field_storage_definitions($settings->getTargetEntityTypeId());
     }
@@ -222,7 +227,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');
-- 
GitLab


From ec1ff6f69bc004073651fa04082865cd42c35edc Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 31 Dec 2024 14:48:49 -0500
Subject: [PATCH 083/173] Add replacement functionality and convert navigation

---
 core/lib/Drupal/Core/Hook/Attribute/Hook.php  |  3 ++
 .../Drupal/Core/Hook/HookCollectorPass.php    | 17 +++++++++++
 core/modules/navigation/navigation.module     | 10 -------
 .../navigation/src/Hook/NavigationHooks.php   |  2 +-
 .../hook_test_replacements.info.yml           |  7 +++++
 .../src/Hook/TestHookReplacements.php         | 30 +++++++++++++++++++
 .../Core/Hook/HookCollectorPassTest.php       | 16 ++++++++++
 7 files changed, 74 insertions(+), 11 deletions(-)
 create mode 100644 core/modules/system/tests/modules/hook_test_replacements/hook_test_replacements.info.yml
 create mode 100644 core/modules/system/tests/modules/hook_test_replacements/src/Hook/TestHookReplacements.php

diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
index 2a4cfc0e9095..debabeda89ba 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -117,12 +117,15 @@ class Hook {
    *   the implementation is in.
    * @param \Drupal\Core\Hook\Order|\Drupal\Core\Hook\ComplexOrder|null $order
    *   (optional) Set the order of the implementation.
+   * @param array|null $replacements
+   *   (optional) An array keyed by modules of hook implementations to remove.
    */
   public function __construct(
     public string $hook,
     public string $method = '',
     public ?string $module = NULL,
     public Order|ComplexOrder|NULL $order = NULL,
+    public array|NULL $replacements = NULL,
   ) {}
 
   /**
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 04c1ca26473c..5ca74d8c1716 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -72,6 +72,7 @@ public function process(ContainerBuilder $container): array {
     $orderGroups = [];
     $orderAttributes = [];
     $moduleFinder = [];
+    $allReplacements = [];
     foreach (array_keys($container->getParameter('container.modules')) as $module) {
       foreach ($collector->moduleHooks[$module] ?? [] as $class => $methods) {
         foreach ($methods as $method => $hooks) {
@@ -93,11 +94,27 @@ public function process(ContainerBuilder $container): array {
                 }
               }
             }
+            if ($hook->hook == 'help' && $hook->module == 'navigation') {
+              $test = 1;
+            }
+            if ($hook->replacements) {
+              foreach ($hook->replacements as $module_replacement => $replacements) {
+                $allReplacements[$module_replacement] = array_merge($allReplacements[$module_replacement] ?? [], $replacements);
+              }
+            }
           }
         }
       }
     }
     $orderGroups = array_map('array_unique', $orderGroups);
+    $allReplacements = array_map('array_unique', $allReplacements);
+
+    foreach ($allReplacements as $module_replacement => $replacements) {
+      foreach($replacements as $replacement) {
+        unset($implementations[$replacement][$module_replacement]);
+        unset($legacyImplementations[$replacement][$module_replacement]);
+      }
+    }
 
     // @todo investigate whether this if() is needed after ModuleHandler::add()
     // is removed.
diff --git a/core/modules/navigation/navigation.module b/core/modules/navigation/navigation.module
index ecf6a31f934f..570f13ba3e4a 100644
--- a/core/modules/navigation/navigation.module
+++ b/core/modules/navigation/navigation.module
@@ -6,16 +6,6 @@
 
 use Drupal\navigation\TopBarRegion;
 
-/**
- * Implements hook_module_implements_alter().
- */
-function navigation_module_implements_alter(&$implementations, $hook): void {
-  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 30c76860885d..fc20a3298b99 100644
--- a/core/modules/navigation/src/Hook/NavigationHooks.php
+++ b/core/modules/navigation/src/Hook/NavigationHooks.php
@@ -55,7 +55,7 @@ public function __construct(
   /**
    * Implements hook_help().
    */
-  #[Hook('help')]
+  #[Hook('help', replacements: ['layout_builder' => ['help']])]
   public function help($route_name, RouteMatchInterface $route_match): ?string {
     switch ($route_name) {
       case 'help.page.navigation':
diff --git a/core/modules/system/tests/modules/hook_test_replacements/hook_test_replacements.info.yml b/core/modules/system/tests/modules/hook_test_replacements/hook_test_replacements.info.yml
new file mode 100644
index 000000000000..d6baedba7fa5
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_test_replacements/hook_test_replacements.info.yml
@@ -0,0 +1,7 @@
+name: Hook test replacements
+type: module
+description: 'Test module used to test hook replacement.'
+package: Testing
+version: VERSION
+core_version_requirement: '*'
+hidden: true
diff --git a/core/modules/system/tests/modules/hook_test_replacements/src/Hook/TestHookReplacements.php b/core/modules/system/tests/modules/hook_test_replacements/src/Hook/TestHookReplacements.php
new file mode 100644
index 000000000000..672f7f6e65ad
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_test_replacements/src/Hook/TestHookReplacements.php
@@ -0,0 +1,30 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_test_replacements\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Add a hook here, then remove it with another attribute.
+ */
+class TestHookReplacements {
+
+  /**
+   * This hook should not be run because the next hook replaces it.
+   */
+  #[Hook('custom_hook1')]
+  public static function hookDoNotRun(): void {
+    $GLOBALS['HookShouldNotRunTestReplacement'] = 'HookShouldNotRunTestReplacement';
+  }
+
+  /**
+   * This hook should run and prevent custom_hook1.
+   */
+  #[Hook('custom_hook2', replacements: ['hook_test_replacements' => ['custom_hook1']])]
+  public static function hookDoRun(): void {
+    $GLOBALS['HookShouldRunTestReplacement'] = 'HookShouldRunTestReplacement';
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
index 7109df1e9357..a21d5cfa3bb0 100644
--- a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
@@ -249,4 +249,20 @@ public function testHookLast(): void {
     $this->assertTrue(isset($GLOBALS['HookRanTestingHookLast']));
   }
 
+  /**
+   * Tests hook replacements.
+   */
+  public function testHookReplacements(): void {
+    $module_installer = $this->container->get('module_installer');
+    $this->assertTrue($module_installer->install(['hook_test_replacements']));
+    $this->assertFalse(isset($GLOBALS['HookShouldRunTestReplacement']));
+    $this->assertFalse(isset($GLOBALS['HookShouldNotRunTestReplacement']));
+    $module_handler = $this->container->get('module_handler');
+    $data = ['hi'];
+    $module_handler->invokeAll('custom_hook1', $data);
+    $module_handler->invokeAll('custom_hook2', $data);
+    $this->assertTrue(isset($GLOBALS['HookShouldRunTestReplacement']));
+    $this->assertFalse(isset($GLOBALS['HookShouldNotRunTestReplacement']));
+  }
+
 }
-- 
GitLab


From b99394e72492a0b05e96cbd735379c2bf0628d00 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 31 Dec 2024 14:53:25 -0500
Subject: [PATCH 084/173] CS

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 5ca74d8c1716..63602b709dc1 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -94,9 +94,6 @@ public function process(ContainerBuilder $container): array {
                 }
               }
             }
-            if ($hook->hook == 'help' && $hook->module == 'navigation') {
-              $test = 1;
-            }
             if ($hook->replacements) {
               foreach ($hook->replacements as $module_replacement => $replacements) {
                 $allReplacements[$module_replacement] = array_merge($allReplacements[$module_replacement] ?? [], $replacements);
@@ -110,7 +107,7 @@ public function process(ContainerBuilder $container): array {
     $allReplacements = array_map('array_unique', $allReplacements);
 
     foreach ($allReplacements as $module_replacement => $replacements) {
-      foreach($replacements as $replacement) {
+      foreach ($replacements as $replacement) {
         unset($implementations[$replacement][$module_replacement]);
         unset($legacyImplementations[$replacement][$module_replacement]);
       }
-- 
GitLab


From 508439c078af6dd189a4c7e747fafdc5b5207974 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 31 Dec 2024 16:16:58 -0500
Subject: [PATCH 085/173] Convert Workspaces

---
 .../workspaces/src/Hook/EntityOperations.php  |  6 ++--
 core/modules/workspaces/workspaces.module     | 35 -------------------
 2 files changed, 4 insertions(+), 37 deletions(-)
 delete mode 100644 core/modules/workspaces/workspaces.module

diff --git a/core/modules/workspaces/src/Hook/EntityOperations.php b/core/modules/workspaces/src/Hook/EntityOperations.php
index c459f4d065e3..aac53b237dc6 100644
--- a/core/modules/workspaces/src/Hook/EntityOperations.php
+++ b/core/modules/workspaces/src/Hook/EntityOperations.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Entity\RevisionableInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Order;
 use Drupal\workspaces\WorkspaceAssociationInterface;
 use Drupal\workspaces\WorkspaceInformationInterface;
 use Drupal\workspaces\WorkspaceManagerInterface;
@@ -72,7 +73,8 @@ public function entityPreload(array $ids, string $entity_type_id): array {
   /**
    * Implements hook_entity_presave().
    */
-  #[Hook('entity_presave')]
+  #[Hook('entity_presave', order: Order::First)]
+  #[Hook('entity_presave', order: Order::Last)]
   public function entityPresave(EntityInterface $entity): void {
     if ($this->shouldSkipOperations($entity)) {
       return;
@@ -129,7 +131,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 a053105f20c3..000000000000
--- 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;
-  }
-}
-- 
GitLab


From 5a9f3b97fb9573320e2a8bf1610ea3e809234005 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 31 Dec 2024 17:08:31 -0500
Subject: [PATCH 086/173] Use simpler ordering option

---
 .../layout_builder_test/src/Hook/LayoutBuilderTestHooks.php  | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

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 d968b97c1e22..5a2a09729bcd 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
@@ -12,7 +12,6 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\Core\Hook\OrderBefore;
-use Drupal\layout_builder\Hook\LayoutBuilderHooks;
 
 /**
  * Hook implementations for layout_builder_test.
@@ -120,9 +119,7 @@ public function layoutBuilderEntityFormDisplayAlter(EntityFormDisplayInterface $
   #[Hook(
     'system_breadcrumb_alter',
     order: new OrderBefore(
-      classesAndMethods: [
-        [LayoutBuilderHooks::class, 'systemBreadcrumbAlter'],
-      ]
+      modules: ['layout_builder']
     )
   )]
   public function systemBreadcrumbAlter(Breadcrumb &$breadcrumb, RouteMatchInterface $route_match, array $context): void {
-- 
GitLab


From 812d387d022af1f54d378bdec4532a16e653abb4 Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Tue, 31 Dec 2024 19:17:57 -0500
Subject: [PATCH 087/173] Rename to remove

---
 core/lib/Drupal/Core/Hook/Attribute/Hook.php    |  4 ++--
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 17 ++++++++---------
 .../navigation/src/Hook/NavigationHooks.php     |  2 +-
 .../hook_test_remove.info.yml}                  |  4 ++--
 .../src/Hook/TestHookRemove.php}                | 10 +++++-----
 .../Core/Hook/HookCollectorPassTest.php         | 10 +++++-----
 6 files changed, 23 insertions(+), 24 deletions(-)
 rename core/modules/system/tests/modules/{hook_test_replacements/hook_test_replacements.info.yml => hook_test_remove/hook_test_remove.info.yml} (50%)
 rename core/modules/system/tests/modules/{hook_test_replacements/src/Hook/TestHookReplacements.php => hook_test_remove/src/Hook/TestHookRemove.php} (55%)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
index debabeda89ba..4c1b9e255bd6 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -117,7 +117,7 @@ class Hook {
    *   the implementation is in.
    * @param \Drupal\Core\Hook\Order|\Drupal\Core\Hook\ComplexOrder|null $order
    *   (optional) Set the order of the implementation.
-   * @param array|null $replacements
+   * @param array|null $remove
    *   (optional) An array keyed by modules of hook implementations to remove.
    */
   public function __construct(
@@ -125,7 +125,7 @@ public function __construct(
     public string $method = '',
     public ?string $module = NULL,
     public Order|ComplexOrder|NULL $order = NULL,
-    public array|NULL $replacements = NULL,
+    public array|NULL $remove = NULL,
   ) {}
 
   /**
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 63602b709dc1..c305ac64e83b 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -72,7 +72,7 @@ public function process(ContainerBuilder $container): array {
     $orderGroups = [];
     $orderAttributes = [];
     $moduleFinder = [];
-    $allReplacements = [];
+    $allRemovals = [];
     foreach (array_keys($container->getParameter('container.modules')) as $module) {
       foreach ($collector->moduleHooks[$module] ?? [] as $class => $methods) {
         foreach ($methods as $method => $hooks) {
@@ -94,9 +94,9 @@ public function process(ContainerBuilder $container): array {
                 }
               }
             }
-            if ($hook->replacements) {
-              foreach ($hook->replacements as $module_replacement => $replacements) {
-                $allReplacements[$module_replacement] = array_merge($allReplacements[$module_replacement] ?? [], $replacements);
+            if ($hook->remove) {
+              foreach ($hook->remove as $module_remove => $removals) {
+                $allRemovals[$module_remove] = array_merge($allRemovals[$module_remove] ?? [], $removals);
               }
             }
           }
@@ -104,12 +104,11 @@ public function process(ContainerBuilder $container): array {
       }
     }
     $orderGroups = array_map('array_unique', $orderGroups);
-    $allReplacements = array_map('array_unique', $allReplacements);
 
-    foreach ($allReplacements as $module_replacement => $replacements) {
-      foreach ($replacements as $replacement) {
-        unset($implementations[$replacement][$module_replacement]);
-        unset($legacyImplementations[$replacement][$module_replacement]);
+    foreach ($allRemovals as $module_remove => $removals) {
+      foreach ($removals as $removal) {
+        unset($implementations[$removal][$module_remove]);
+        unset($legacyImplementations[$removal][$module_remove]);
       }
     }
 
diff --git a/core/modules/navigation/src/Hook/NavigationHooks.php b/core/modules/navigation/src/Hook/NavigationHooks.php
index fc20a3298b99..b5984a5f9512 100644
--- a/core/modules/navigation/src/Hook/NavigationHooks.php
+++ b/core/modules/navigation/src/Hook/NavigationHooks.php
@@ -55,7 +55,7 @@ public function __construct(
   /**
    * Implements hook_help().
    */
-  #[Hook('help', replacements: ['layout_builder' => ['help']])]
+  #[Hook('help', remove: ['layout_builder' => ['help']])]
   public function help($route_name, RouteMatchInterface $route_match): ?string {
     switch ($route_name) {
       case 'help.page.navigation':
diff --git a/core/modules/system/tests/modules/hook_test_replacements/hook_test_replacements.info.yml b/core/modules/system/tests/modules/hook_test_remove/hook_test_remove.info.yml
similarity index 50%
rename from core/modules/system/tests/modules/hook_test_replacements/hook_test_replacements.info.yml
rename to core/modules/system/tests/modules/hook_test_remove/hook_test_remove.info.yml
index d6baedba7fa5..9245dd4208f7 100644
--- a/core/modules/system/tests/modules/hook_test_replacements/hook_test_replacements.info.yml
+++ b/core/modules/system/tests/modules/hook_test_remove/hook_test_remove.info.yml
@@ -1,6 +1,6 @@
-name: Hook test replacements
+name: Hook test removal
 type: module
-description: 'Test module used to test hook replacement.'
+description: 'Test module used to test hook removal.'
 package: Testing
 version: VERSION
 core_version_requirement: '*'
diff --git a/core/modules/system/tests/modules/hook_test_replacements/src/Hook/TestHookReplacements.php b/core/modules/system/tests/modules/hook_test_remove/src/Hook/TestHookRemove.php
similarity index 55%
rename from core/modules/system/tests/modules/hook_test_replacements/src/Hook/TestHookReplacements.php
rename to core/modules/system/tests/modules/hook_test_remove/src/Hook/TestHookRemove.php
index 672f7f6e65ad..3065cd1a727c 100644
--- a/core/modules/system/tests/modules/hook_test_replacements/src/Hook/TestHookReplacements.php
+++ b/core/modules/system/tests/modules/hook_test_remove/src/Hook/TestHookRemove.php
@@ -2,29 +2,29 @@
 
 declare(strict_types=1);
 
-namespace Drupal\hook_test_replacements\Hook;
+namespace Drupal\hook_test_remove\Hook;
 
 use Drupal\Core\Hook\Attribute\Hook;
 
 /**
  * Add a hook here, then remove it with another attribute.
  */
-class TestHookReplacements {
+class TestHookRemove {
 
   /**
    * This hook should not be run because the next hook replaces it.
    */
   #[Hook('custom_hook1')]
   public static function hookDoNotRun(): void {
-    $GLOBALS['HookShouldNotRunTestReplacement'] = 'HookShouldNotRunTestReplacement';
+    $GLOBALS['HookShouldNotRunTestRemove'] = 'HookShouldNotRunTestRemove';
   }
 
   /**
    * This hook should run and prevent custom_hook1.
    */
-  #[Hook('custom_hook2', replacements: ['hook_test_replacements' => ['custom_hook1']])]
+  #[Hook('custom_hook2', remove: ['hook_test_remove' => ['custom_hook1']])]
   public static function hookDoRun(): void {
-    $GLOBALS['HookShouldRunTestReplacement'] = 'HookShouldRunTestReplacement';
+    $GLOBALS['HookShouldRunTestRemove'] = 'HookShouldRunTestRemove';
   }
 
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
index a21d5cfa3bb0..29a5984545a7 100644
--- a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
@@ -254,15 +254,15 @@ public function testHookLast(): void {
    */
   public function testHookReplacements(): void {
     $module_installer = $this->container->get('module_installer');
-    $this->assertTrue($module_installer->install(['hook_test_replacements']));
-    $this->assertFalse(isset($GLOBALS['HookShouldRunTestReplacement']));
-    $this->assertFalse(isset($GLOBALS['HookShouldNotRunTestReplacement']));
+    $this->assertTrue($module_installer->install(['hook_test_remove']));
+    $this->assertFalse(isset($GLOBALS['HookShouldRunTestRemove']));
+    $this->assertFalse(isset($GLOBALS['HookShouldNotRunTestRemove']));
     $module_handler = $this->container->get('module_handler');
     $data = ['hi'];
     $module_handler->invokeAll('custom_hook1', $data);
     $module_handler->invokeAll('custom_hook2', $data);
-    $this->assertTrue(isset($GLOBALS['HookShouldRunTestReplacement']));
-    $this->assertFalse(isset($GLOBALS['HookShouldNotRunTestReplacement']));
+    $this->assertTrue(isset($GLOBALS['HookShouldRunTestRemove']));
+    $this->assertFalse(isset($GLOBALS['HookShouldNotRunTestRemove']));
   }
 
 }
-- 
GitLab


From 8cae56ca2ddc2c05347972da6819b089b6c67704 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Wed, 1 Jan 2025 10:31:45 -0500
Subject: [PATCH 088/173] Ordering

---
 core/lib/Drupal/Core/Hook/Attribute/Hook.php | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
index 4c1b9e255bd6..74159d2c1205 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -34,8 +34,13 @@
  *   }
  *   @endcode
  *
- * Ordering hook implementations can be done by implementing
- * hook_module_implements_alter.
+ * Ordering hook implementations can be done by using the order parameter.
+ *
+ * @see https://www.drupal.org/node/3493962
+ *
+ * Removing hook implementations can be done by using the remove parameter.
+ *
+ * @see https://www.drupal.org/node/3496786
  *
  * Classes that use this annotation on the class or on their methods are
  * automatically registered as autowired services with the class name as the
-- 
GitLab


From e427b9d8379a38125b402c358b33e5a1589d5e2c Mon Sep 17 00:00:00 2001
From: Andrei Mateescu <andrei@amateescu.me>
Date: Thu, 2 Jan 2025 21:04:09 +0200
Subject: [PATCH 089/173] Fix Workspaces presave implementations.

---
 core/modules/workspaces/src/Hook/EntityOperations.php | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/core/modules/workspaces/src/Hook/EntityOperations.php b/core/modules/workspaces/src/Hook/EntityOperations.php
index aac53b237dc6..1fbe0cc7d446 100644
--- a/core/modules/workspaces/src/Hook/EntityOperations.php
+++ b/core/modules/workspaces/src/Hook/EntityOperations.php
@@ -74,7 +74,6 @@ public function entityPreload(array $ids, string $entity_type_id): array {
    * Implements hook_entity_presave().
    */
   #[Hook('entity_presave', order: Order::First)]
-  #[Hook('entity_presave', order: Order::Last)]
   public function entityPresave(EntityInterface $entity): void {
     if ($this->shouldSkipOperations($entity)) {
       return;
@@ -115,6 +114,16 @@ public function entityPresave(EntityInterface $entity): void {
       $field_name = $entity->getEntityType()->getRevisionMetadataKey('workspace');
       $entity->{$field_name}->target_id = $this->workspaceManager->getActiveWorkspace()->id();
     }
+  }
+
+  /**
+   * Implements hook_entity_presave().
+   */
+  #[Hook('entity_presave', order: Order::Last)]
+  public function entityPresaveLast(EntityInterface $entity): void {
+    if ($this->shouldSkipOperations($entity)) {
+      return;
+    }
 
     // When a new published entity is inserted in a non-default workspace, we
     // actually want two revisions to be saved:
-- 
GitLab


From 33cd6fb966fbecfa03519223deff94320759694b Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Fri, 3 Jan 2025 11:23:00 -0500
Subject: [PATCH 090/173] Handle reordering other hook implementations

---
 core/lib/Drupal/Core/Hook/Attribute/Hook.php   | 15 +++++++--------
 .../lib/Drupal/Core/Hook/HookCollectorPass.php |  6 +++---
 .../workspaces/src/Hook/EntityOperations.php   | 18 ++++++++----------
 3 files changed, 18 insertions(+), 21 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
index 74159d2c1205..96ed909f8e00 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -99,18 +99,14 @@
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
 class Hook {
 
-  /**
-   * The class the hook implementation is in.
-   *
-   * @var string
-   */
-  public string $class = '';
-
   /**
    * Constructs a Hook attribute object.
    *
    * @param string $hook
    *   The short hook name, without the 'hook_' prefix.
+   * @param string $class
+   *   (optional) The class name. This should only be used when ordering on
+   *   behalf of another hook.
    * @param string $method
    *   (optional) The method name. If this attribute is on a method, this
    *   parameter is not required. If this attribute is on a class and this
@@ -127,6 +123,7 @@ class Hook {
    */
   public function __construct(
     public string $hook,
+    public string $class = '',
     public string $method = '',
     public ?string $module = NULL,
     public Order|ComplexOrder|NULL $order = NULL,
@@ -144,7 +141,9 @@ public function __construct(
    *   The method for the hook.
    */
   public function set(string $class, string $module, string $method): void {
-    $this->class = $class;
+    if (!$this->class) {
+      $this->class = $class;
+    }
     if (!$this->module) {
       $this->module = $module;
     }
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index c305ac64e83b..2de66a91ecdd 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -79,12 +79,12 @@ public function process(ContainerBuilder $container): array {
           foreach ($hooks as $hook) {
             assert($hook instanceof Hook);
             $hook->set(class: $class, module: $module, method: $method);
-            if ($class !== ProceduralCall::class) {
+            if ($hook->class !== ProceduralCall::class) {
               self::checkForProceduralOnlyHooks($hook);
             }
             $legacyImplementations[$hook->hook][$hook->module] = '';
-            $implementations[$hook->hook][$hook->module][$class][] = $hook->method;
-            $moduleFinder[$class][$method] = $hook->module;
+            $implementations[$hook->hook][$hook->module][$hook->class][$hook->method] = $hook->method;
+            $moduleFinder[$hook->class][$method] = $hook->module;
             if ($hook->order) {
               $orderAttributes[] = $hook;
               if ($hook->order instanceof ComplexOrder && ($group = $hook->order->group)) {
diff --git a/core/modules/workspaces/src/Hook/EntityOperations.php b/core/modules/workspaces/src/Hook/EntityOperations.php
index 1fbe0cc7d446..69d6eef9a953 100644
--- a/core/modules/workspaces/src/Hook/EntityOperations.php
+++ b/core/modules/workspaces/src/Hook/EntityOperations.php
@@ -13,6 +13,8 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\Core\Hook\Order;
+use Drupal\Core\Hook\OrderBefore;
+use Drupal\content_moderation\Hook\ContentModerationHooks;
 use Drupal\workspaces\WorkspaceAssociationInterface;
 use Drupal\workspaces\WorkspaceInformationInterface;
 use Drupal\workspaces\WorkspaceManagerInterface;
@@ -74,6 +76,12 @@ public function entityPreload(array $ids, string $entity_type_id): array {
    * Implements hook_entity_presave().
    */
   #[Hook('entity_presave', order: Order::First)]
+  #[Hook('entity_presave',
+    module: 'content_moderation',
+    class: ContentModerationHooks::class,
+    method: 'entityPresave',
+    order: new OrderBefore(['workspaces'])
+  )]
   public function entityPresave(EntityInterface $entity): void {
     if ($this->shouldSkipOperations($entity)) {
       return;
@@ -114,16 +122,6 @@ public function entityPresave(EntityInterface $entity): void {
       $field_name = $entity->getEntityType()->getRevisionMetadataKey('workspace');
       $entity->{$field_name}->target_id = $this->workspaceManager->getActiveWorkspace()->id();
     }
-  }
-
-  /**
-   * Implements hook_entity_presave().
-   */
-  #[Hook('entity_presave', order: Order::Last)]
-  public function entityPresaveLast(EntityInterface $entity): void {
-    if ($this->shouldSkipOperations($entity)) {
-      return;
-    }
 
     // When a new published entity is inserted in a non-default workspace, we
     // actually want two revisions to be saved:
-- 
GitLab


From fcc6f4ecf07dbc3225547b98365b9f5d46d79977 Mon Sep 17 00:00:00 2001
From: Ghost Of Drupal Past <drupal@negyesi.net>
Date: Fri, 3 Jan 2025 18:00:05 +0100
Subject: [PATCH 091/173] i really hate this bullshit

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 4 ----
 core/lib/Drupal/Core/Hook/HookPriority.php      | 2 --
 2 files changed, 6 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 2de66a91ecdd..6c070b70a81e 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -137,8 +137,6 @@ public function process(ContainerBuilder $container): array {
    *   Modules that implement legacy hooks.
    * @param array $orderGroups
    *   Groups of hooks to reorder.
-   *
-   * @return void
    */
   protected static function registerImplementations(ContainerBuilder $container, HookCollectorPass $collector, array $implementations, array $legacyImplementations, array $orderGroups): void {
     $container->register(ProceduralCall::class, ProceduralCall::class)
@@ -202,8 +200,6 @@ protected static function registerImplementations(ContainerBuilder $container, H
    *   An array keyed by the class and method of a hook implementation, value
    *   is the module. This is not necessarily the same as the module the class
    *   is in because the implementation might be on behalf of another module.
-   *
-   * @return void
    */
   protected static function reOrderImplementations(ContainerBuilder $container, array $orderAttributes, array $orderGroups, array $implementations, array $moduleFinder): void {
     $hookPriority = new HookPriority($container);
diff --git a/core/lib/Drupal/Core/Hook/HookPriority.php b/core/lib/Drupal/Core/Hook/HookPriority.php
index 72d931963d47..9a9fe4e6321a 100644
--- a/core/lib/Drupal/Core/Hook/HookPriority.php
+++ b/core/lib/Drupal/Core/Hook/HookPriority.php
@@ -104,8 +104,6 @@ public function change(string $event, Hook $hook, ?array $other_specifiers = NUL
    *   hook implementation to be changed.
    * @param int $priority
    *   The new priority.
-   *
-   * @return void
    */
   public function set(string $class, int $key, int $priority): void {
     $definition = $this->container->findDefinition($class);
-- 
GitLab


From aa3d422c14bed3cd46ada0af75803eb1b730e4fd Mon Sep 17 00:00:00 2001
From: Ghost Of Drupal Past <drupal@negyesi.net>
Date: Fri, 3 Jan 2025 18:11:42 +0100
Subject: [PATCH 092/173] meh

---
 core/lib/Drupal/Core/Hook/Attribute/Hook.php    | 8 ++++----
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 2 +-
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
index 96ed909f8e00..4bc3e4612692 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -104,9 +104,6 @@ class Hook {
    *
    * @param string $hook
    *   The short hook name, without the 'hook_' prefix.
-   * @param string $class
-   *   (optional) The class name. This should only be used when ordering on
-   *   behalf of another hook.
    * @param string $method
    *   (optional) The method name. If this attribute is on a method, this
    *   parameter is not required. If this attribute is on a class and this
@@ -120,14 +117,17 @@ class Hook {
    *   (optional) Set the order of the implementation.
    * @param array|null $remove
    *   (optional) An array keyed by modules of hook implementations to remove.
+   * @param string $class
+   *   (optional) The class name. This should only be used when ordering on
+   *   behalf of another hook.
    */
   public function __construct(
     public string $hook,
-    public string $class = '',
     public string $method = '',
     public ?string $module = NULL,
     public Order|ComplexOrder|NULL $order = NULL,
     public array|NULL $remove = NULL,
+    public string $class = '',
   ) {}
 
   /**
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 6c070b70a81e..7c3323af2b3f 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -394,7 +394,7 @@ protected static function filterIterator(\SplFileInfo $fileInfo, $key, \Recursiv
    *   The name of function implementing the hook.
    */
   protected function addProceduralImplementation(\SplFileInfo $fileinfo, string $hook, string $module, string $function): void {
-    $this->moduleHooks[$module][ProceduralCall::class][$function] = [new Hook($hook, $module . '_' . $hook)];
+    $this->moduleHooks[$module][ProceduralCall::class][$function] = [new Hook($hook, method: $module . '_' . $hook)];
     if ($hook === 'hook_info') {
       $this->hookInfo[] = $function;
     }
-- 
GitLab


From a6a04615627ca9f77e63c6435af39e25daa75a70 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Fri, 3 Jan 2025 13:07:56 -0500
Subject: [PATCH 093/173] Fix optional hook classes

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 7c3323af2b3f..f6a81f92ee90 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -78,6 +78,9 @@ public function process(ContainerBuilder $container): array {
         foreach ($methods as $method => $hooks) {
           foreach ($hooks as $hook) {
             assert($hook instanceof Hook);
+            if ($hook->class && !class_exists($hook->class, FALSE)) {
+              continue;
+            }
             $hook->set(class: $class, module: $module, method: $method);
             if ($hook->class !== ProceduralCall::class) {
               self::checkForProceduralOnlyHooks($hook);
-- 
GitLab


From 8e7e7a5a994c01544dd5ee6f299e703c40c3e146 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Fri, 3 Jan 2025 17:20:22 -0500
Subject: [PATCH 094/173] Named remove and override

---
 core/lib/Drupal/Core/Hook/Attribute/Hook.php  | 14 +++++-----
 .../Core/Hook/Attribute/OverrideHook.php      | 24 +++++++++++++++++
 .../Drupal/Core/Hook/Attribute/RemoveHook.php | 20 ++++++++++++++
 .../Drupal/Core/Hook/HookCollectorPass.php    | 26 +++++++++----------
 .../navigation/src/Hook/NavigationHooks.php   |  5 +++-
 .../src/Hook/TestHookRemove.php               |  4 ++-
 .../workspaces/src/Hook/EntityOperations.php  |  5 ++--
 7 files changed, 74 insertions(+), 24 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Hook/Attribute/OverrideHook.php
 create mode 100644 core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php

diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
index 4bc3e4612692..a48e3a52c289 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -99,6 +99,13 @@
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
 class Hook {
 
+  /**
+   * The class the hook implementation is in.
+   *
+   * @var string
+   */
+  public string $class = '';
+
   /**
    * Constructs a Hook attribute object.
    *
@@ -115,19 +122,12 @@ class Hook {
    *   the implementation is in.
    * @param \Drupal\Core\Hook\Order|\Drupal\Core\Hook\ComplexOrder|null $order
    *   (optional) Set the order of the implementation.
-   * @param array|null $remove
-   *   (optional) An array keyed by modules of hook implementations to remove.
-   * @param string $class
-   *   (optional) The class name. This should only be used when ordering on
-   *   behalf of another hook.
    */
   public function __construct(
     public string $hook,
     public string $method = '',
     public ?string $module = NULL,
     public Order|ComplexOrder|NULL $order = NULL,
-    public array|NULL $remove = NULL,
-    public string $class = '',
   ) {}
 
   /**
diff --git a/core/lib/Drupal/Core/Hook/Attribute/OverrideHook.php b/core/lib/Drupal/Core/Hook/Attribute/OverrideHook.php
new file mode 100644
index 000000000000..cb5cab6f09e9
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Attribute/OverrideHook.php
@@ -0,0 +1,24 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Attribute;
+
+use Drupal\Core\Hook\ComplexOrder;
+use Drupal\Core\Hook\Order;
+
+#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class OverrideHook extends Hook {
+
+  public function __construct(
+    string $hook,
+    string $class,
+    string $module,
+    string $method,
+    Order|ComplexOrder $order,
+  ) {
+    parent::__construct($hook, method: $method, module: $module, order: $order);
+    $this->class = $class;
+  }
+
+}
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 000000000000..ee9fb2dc868f
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
@@ -0,0 +1,20 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Attribute;
+
+#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
+class RemoveHook extends Hook {
+
+  public function __construct(
+    string $hook,
+    string $class,
+    string $module,
+    string $method,
+  ) {
+    parent::__construct($hook, module: $module, method: $method);
+    $this->class = $class;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index f6a81f92ee90..b8f8189196be 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -10,6 +10,8 @@
 use Drupal\Core\Extension\ProceduralCall;
 use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\Core\Hook\Attribute\OverrideHook;
+use Drupal\Core\Hook\Attribute\RemoveHook;
 use Drupal\Core\Hook\Attribute\StopProceduralHookScan;
 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -69,16 +71,21 @@ class HookCollectorPass implements CompilerPassInterface {
   public function process(ContainerBuilder $container): array {
     $collector = static::collectAllHookImplementations($container->getParameter('container.modules'), $container);
     $implementations = [];
+    $legacyImplementations = [];
     $orderGroups = [];
     $orderAttributes = [];
     $moduleFinder = [];
-    $allRemovals = [];
+    $removals = [];
     foreach (array_keys($container->getParameter('container.modules')) as $module) {
       foreach ($collector->moduleHooks[$module] ?? [] as $class => $methods) {
         foreach ($methods as $method => $hooks) {
           foreach ($hooks as $hook) {
             assert($hook instanceof Hook);
-            if ($hook->class && !class_exists($hook->class, FALSE)) {
+            if ($hook instanceof OverrideHook && !class_exists($hook->class, FALSE)) {
+              continue;
+            }
+            if ($hook instanceof RemoveHook) {
+              $removals[] = $hook;
               continue;
             }
             $hook->set(class: $class, module: $module, method: $method);
@@ -97,22 +104,15 @@ public function process(ContainerBuilder $container): array {
                 }
               }
             }
-            if ($hook->remove) {
-              foreach ($hook->remove as $module_remove => $removals) {
-                $allRemovals[$module_remove] = array_merge($allRemovals[$module_remove] ?? [], $removals);
-              }
-            }
           }
         }
       }
     }
     $orderGroups = array_map('array_unique', $orderGroups);
 
-    foreach ($allRemovals as $module_remove => $removals) {
-      foreach ($removals as $removal) {
-        unset($implementations[$removal][$module_remove]);
-        unset($legacyImplementations[$removal][$module_remove]);
-      }
+    foreach ($removals as $hook) {
+      unset($legacyImplementations[$hook->hook][$hook->module]);
+      unset($implementations[$hook->hook][$hook->module][$hook->class][$hook->method]);
     }
 
     // @todo investigate whether this if() is needed after ModuleHandler::add()
@@ -479,7 +479,7 @@ public static function checkForProceduralOnlyHooks(Hook $hook, string $class = '
    */
   protected static function getAttributeInstances(array $attributes, array $reflections): array {
     foreach ($reflections as $reflection) {
-      if ($reflection_attributes = $reflection->getAttributes()) {
+      if ($reflection_attributes = $reflection->getAttributes(Hook::class, \ReflectionAttribute::IS_INSTANCEOF)) {
         $method = $reflection instanceof \ReflectionMethod ? $reflection->getName() : '__invoke';
         $attributes[$method] = array_map(fn (\ReflectionAttribute $ra) => $ra->newInstance(), $reflection_attributes);
       }
diff --git a/core/modules/navigation/src/Hook/NavigationHooks.php b/core/modules/navigation/src/Hook/NavigationHooks.php
index b5984a5f9512..ee30220ae773 100644
--- a/core/modules/navigation/src/Hook/NavigationHooks.php
+++ b/core/modules/navigation/src/Hook/NavigationHooks.php
@@ -7,10 +7,12 @@
 use Drupal\Core\Config\Action\ConfigActionManager;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Attribute\RemoveHook;
 use Drupal\Core\Hook\Order;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\layout_builder\Hook\LayoutBuilderHooks;
 use Drupal\navigation\NavigationContentLinks;
 use Drupal\navigation\NavigationRenderer;
 use Drupal\navigation\Plugin\SectionStorage\NavigationSectionStorage;
@@ -55,7 +57,8 @@ public function __construct(
   /**
    * Implements hook_help().
    */
-  #[Hook('help', remove: ['layout_builder' => ['help']])]
+  #[Hook('help')]
+  #[RemoveHook('help', LayoutBuilderHooks::class, 'layout_builder', 'help')]
   public function help($route_name, RouteMatchInterface $route_match): ?string {
     switch ($route_name) {
       case 'help.page.navigation':
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
index 3065cd1a727c..1c7137863cd2 100644
--- 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
@@ -5,6 +5,7 @@
 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.
@@ -22,7 +23,8 @@ public static function hookDoNotRun(): void {
   /**
    * This hook should run and prevent custom_hook1.
    */
-  #[Hook('custom_hook2', remove: ['hook_test_remove' => ['custom_hook1']])]
+  #[Hook('custom_hook2')]
+  #[RemoveHook('custom_hook1', self::class, 'hook_test_remove', 'hookDoNotRun')]
   public static function hookDoRun(): void {
     $GLOBALS['HookShouldRunTestRemove'] = 'HookShouldRunTestRemove';
   }
diff --git a/core/modules/workspaces/src/Hook/EntityOperations.php b/core/modules/workspaces/src/Hook/EntityOperations.php
index 69d6eef9a953..e0dfe0a15b0e 100644
--- a/core/modules/workspaces/src/Hook/EntityOperations.php
+++ b/core/modules/workspaces/src/Hook/EntityOperations.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Entity\RevisionableInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Attribute\OverrideHook;
 use Drupal\Core\Hook\Order;
 use Drupal\Core\Hook\OrderBefore;
 use Drupal\content_moderation\Hook\ContentModerationHooks;
@@ -76,9 +77,9 @@ public function entityPreload(array $ids, string $entity_type_id): array {
    * Implements hook_entity_presave().
    */
   #[Hook('entity_presave', order: Order::First)]
-  #[Hook('entity_presave',
-    module: 'content_moderation',
+  #[OverrideHook('entity_presave',
     class: ContentModerationHooks::class,
+    module: 'content_moderation',
     method: 'entityPresave',
     order: new OrderBefore(['workspaces'])
   )]
-- 
GitLab


From a43d66fb484f507767f4c742311d1bb56a22d4c5 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Fri, 3 Jan 2025 17:26:41 -0500
Subject: [PATCH 095/173] PHP stan

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index b8f8189196be..8e1a1fe795fa 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -119,7 +119,7 @@ public function process(ContainerBuilder $container): array {
     // is removed.
     // @see https://www.drupal.org/project/drupal/issues/3481778
     if (count($container->getDefinitions()) > 1) {
-      static::registerImplementations($container, $collector, $implementations, $legacyImplementations ?? [], $orderGroups);
+      static::registerImplementations($container, $collector, $implementations, $legacyImplementations, $orderGroups);
       static::reOrderImplementations($container, $orderAttributes, $orderGroups, $implementations, $moduleFinder);
     }
     return $implementations;
-- 
GitLab


From eb30ee27cb4bf9f946465afa79123c8cb4ec032d Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Fri, 3 Jan 2025 23:44:21 -0500
Subject: [PATCH 096/173] Handle removal and override cleaner

---
 .../Core/Hook/Attribute/OverrideHook.php      | 22 +++++++++++++--
 .../Drupal/Core/Hook/Attribute/RemoveHook.php | 16 +++++++++--
 .../Drupal/Core/Hook/HookCollectorPass.php    | 27 ++++++++++---------
 3 files changed, 49 insertions(+), 16 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/OverrideHook.php b/core/lib/Drupal/Core/Hook/Attribute/OverrideHook.php
index cb5cab6f09e9..ebce073a46f0 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/OverrideHook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/OverrideHook.php
@@ -7,17 +7,35 @@
 use Drupal\Core\Hook\ComplexOrder;
 use Drupal\Core\Hook\Order;
 
+/**
+ * Attribute for overriding the order of another Hook.
+ *
+ * When another hook needs to be ordered provide an OverrideHook attribute
+ * that specifies the new ordering attribute.
+ */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
 class OverrideHook extends Hook {
 
+  /**
+   * Constructs a Hook attribute object.
+   *
+   * @param string $hook
+   *   The short hook name, without the 'hook_' prefix.
+   * @param string $class
+   *   The class the implementation to modify is in. This allows one module to
+   *   affect the order of another module's hook.
+   * @param string $method
+   *   The method name of the implementation to modify.
+   * @param \Drupal\Core\Hook\Order|\Drupal\Core\Hook\ComplexOrder $order
+   *   Set the order of the implementation.
+   */
   public function __construct(
     string $hook,
     string $class,
-    string $module,
     string $method,
     Order|ComplexOrder $order,
   ) {
-    parent::__construct($hook, method: $method, module: $module, order: $order);
+    parent::__construct($hook, method: $method, order: $order);
     $this->class = $class;
   }
 
diff --git a/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php b/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
index ee9fb2dc868f..1ba132742a87 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
@@ -4,16 +4,28 @@
 
 namespace Drupal\Core\Hook\Attribute;
 
+/**
+ * Attribute for removing another implementation.
+ */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
 class RemoveHook extends Hook {
 
+  /**
+   * Constructs a Hook attribute object.
+   *
+   * @param string $hook
+   *   The short hook name, without the 'hook_' prefix.
+   * @param string $class
+   *   The class the implementation to remove is in.
+   * @param string $method
+   *   The method name of the implementation to remove.
+   */
   public function __construct(
     string $hook,
     string $class,
-    string $module,
     string $method,
   ) {
-    parent::__construct($hook, module: $module, method: $method);
+    parent::__construct($hook, method: $method);
     $this->class = $class;
   }
 
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 8e1a1fe795fa..83912d8fdc74 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -75,26 +75,17 @@ public function process(ContainerBuilder $container): array {
     $orderGroups = [];
     $orderAttributes = [];
     $moduleFinder = [];
+    /** @var \Drupal\Core\Hook\Attribute\RemoveHook[] $removals */
     $removals = [];
     foreach (array_keys($container->getParameter('container.modules')) as $module) {
       foreach ($collector->moduleHooks[$module] ?? [] as $class => $methods) {
         foreach ($methods as $method => $hooks) {
           foreach ($hooks as $hook) {
             assert($hook instanceof Hook);
-            if ($hook instanceof OverrideHook && !class_exists($hook->class, FALSE)) {
-              continue;
-            }
             if ($hook instanceof RemoveHook) {
               $removals[] = $hook;
               continue;
             }
-            $hook->set(class: $class, module: $module, method: $method);
-            if ($hook->class !== ProceduralCall::class) {
-              self::checkForProceduralOnlyHooks($hook);
-            }
-            $legacyImplementations[$hook->hook][$hook->module] = '';
-            $implementations[$hook->hook][$hook->module][$hook->class][$hook->method] = $hook->method;
-            $moduleFinder[$hook->class][$method] = $hook->module;
             if ($hook->order) {
               $orderAttributes[] = $hook;
               if ($hook->order instanceof ComplexOrder && ($group = $hook->order->group)) {
@@ -104,6 +95,16 @@ public function process(ContainerBuilder $container): array {
                 }
               }
             }
+            if ($hook instanceof OverrideHook) {
+              continue;
+            }
+            if ($class !== ProceduralCall::class) {
+              self::checkForProceduralOnlyHooks($hook);
+            }
+            $hook->set(... compact('class', 'method', 'module'));
+            $legacyImplementations[$hook->hook][$hook->module] = '';
+            $implementations[$hook->hook][$hook->module][$class][$hook->method] = $hook->method;
+            $moduleFinder[$class][$hook->method] = $hook->module;
           }
         }
       }
@@ -111,8 +112,10 @@ public function process(ContainerBuilder $container): array {
     $orderGroups = array_map('array_unique', $orderGroups);
 
     foreach ($removals as $hook) {
-      unset($legacyImplementations[$hook->hook][$hook->module]);
-      unset($implementations[$hook->hook][$hook->module][$hook->class][$hook->method]);
+      if ($module = ($moduleFinder[$hook->class][$hook->method] ?? '')) {
+        unset($legacyImplementations[$hook->hook][$module]);
+        unset($implementations[$hook->hook][$module][$hook->class][$hook->method]);
+      }
     }
 
     // @todo investigate whether this if() is needed after ModuleHandler::add()
-- 
GitLab


From c4326d5dce8b382a14c4edce94056b6406426b00 Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Fri, 3 Jan 2025 23:59:29 -0500
Subject: [PATCH 097/173] Override last

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 41 +++++++++++++++----
 1 file changed, 32 insertions(+), 9 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 83912d8fdc74..9aca95cacebb 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -77,6 +77,8 @@ public function process(ContainerBuilder $container): array {
     $moduleFinder = [];
     /** @var \Drupal\Core\Hook\Attribute\RemoveHook[] $removals */
     $removals = [];
+    /** @var \Drupal\Core\Hook\Attribute\OverrideHook[] $overrides */
+    $overrides = [];
     foreach (array_keys($container->getParameter('container.modules')) as $module) {
       foreach ($collector->moduleHooks[$module] ?? [] as $class => $methods) {
         foreach ($methods as $method => $hooks) {
@@ -86,18 +88,14 @@ public function process(ContainerBuilder $container): array {
               $removals[] = $hook;
               continue;
             }
-            if ($hook->order) {
-              $orderAttributes[] = $hook;
-              if ($hook->order instanceof ComplexOrder && ($group = $hook->order->group)) {
-                $group[] = $hook->hook;
-                foreach ($group as $extraHook) {
-                  $orderGroups[$extraHook] = array_merge($orderGroups[$extraHook] ?? [], $group);
-                }
-              }
-            }
             if ($hook instanceof OverrideHook) {
+              // Gather overrides to ensure these can run last.
+              $overrides[] = $hook;
               continue;
             }
+            if ($hook->order) {
+              $this->gatherOrderInformation($hook, $orderAttributes, $orderGroups);
+            }
             if ($class !== ProceduralCall::class) {
               self::checkForProceduralOnlyHooks($hook);
             }
@@ -118,6 +116,11 @@ public function process(ContainerBuilder $container): array {
       }
     }
 
+    foreach ($overrides as $hook) {
+      // Append overrides last.
+      $this->gatherOrderInformation($hook, $orderAttributes, $orderGroups);
+    }
+
     // @todo investigate whether this if() is needed after ModuleHandler::add()
     // is removed.
     // @see https://www.drupal.org/project/drupal/issues/3481778
@@ -128,6 +131,26 @@ public function process(ContainerBuilder $container): array {
     return $implementations;
   }
 
+  /**
+   * Gather ordering information.
+   *
+   * @param \Drupal\Core\Hook\Attributes\Hook $hook
+   *   The hook with ordering information.
+   * @param array $orderAttributes
+   *   All attributes related to ordering.
+   * @param array $orderGroups
+   *   Groups to order by.
+   */
+  protected function gatherOrderInformation(Hook $hook, array &$orderAttributes, array &$orderGroups): void {
+    $orderAttributes[] = $hook;
+    if ($hook->order instanceof ComplexOrder && ($group = $hook->order->group)) {
+      $group[] = $hook->hook;
+      foreach ($group as $extraHook) {
+        $orderGroups[$extraHook] = array_merge($orderGroups[$extraHook] ?? [], $group);
+      }
+    }
+  }
+
   /**
    * Register hook implementations as event listeners.
    *
-- 
GitLab


From bd8cddad8b2bd407a6acdf40c6cbd1082b25552e Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Sat, 4 Jan 2025 00:21:18 -0500
Subject: [PATCH 098/173] Test HookOverride

---
 .../src/Hook/TestHookOverrideHookFirst.php    | 45 +++++++++++++++++++
 .../src/Hook/TestHookOverrideHookSecond.php   | 39 ++++++++++++++++
 .../src/Hook/TestHookRemove.php               |  6 ++-
 .../Core/Hook/HookCollectorPassTest.php       | 22 ++++++++-
 4 files changed, 109 insertions(+), 3 deletions(-)
 create mode 100644 core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOverrideHookFirst.php
 create mode 100644 core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOverrideHookSecond.php

diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOverrideHookFirst.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOverrideHookFirst.php
new file mode 100644
index 000000000000..5fc5d6ae056c
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOverrideHookFirst.php
@@ -0,0 +1,45 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_first_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\OrderAfter;
+use Drupal\Core\Hook\Attribute\OverrideHook;
+use Drupal\hook_order_last_alphabetically\Hook\TestHookOverrideHookSecond;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering permutation.
+ */
+class TestHookOverrideHookFirst {
+
+  /**
+   * This pair tests OverrideHook.
+   */
+  #[Hook('custom_hook_override')]
+  #[OverrideHook(
+    'custom_hook_override',
+    class: TestHookOverrideHookSecond::class,
+    method: 'customHookOverride',
+    order: new OrderAfter(
+      classesAndMethods: [[TestHookOverrideHookFirst::class, 'customHookOverride']],
+    )
+  )]
+  public static function customHookOverride(): void {
+    // This normally would run first.
+    // We override that order in hook_order_second_alphabetically.
+    // We override, that order here with OverrideHook.
+    $GLOBALS['HookRanTestingOverrideHookFirstAlpha'] = 'HookRanTestingOverrideHookFirstAlpha';
+  }
+
+}
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOverrideHookSecond.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOverrideHookSecond.php
new file mode 100644
index 000000000000..bb7d26a6c311
--- /dev/null
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOverrideHookSecond.php
@@ -0,0 +1,39 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\hook_order_last_alphabetically\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\Order;
+
+/**
+ * Hook implementations for verifying ordering hooks by attributes.
+ *
+ * We must ensure that the order of the modules is expected and then change
+ * the order that the hooks are run in order to verify. This module
+ * comes in a pair first alphabetically and last alphabetically.
+ *
+ * In the normal order a hook implemented by first alphabetically would run
+ * before the same hook in last alphabetically.
+ *
+ * Each method pair tests one hook ordering permutation.
+ */
+class TestHookOverrideHookSecond {
+
+  /**
+   * This pair tests OverrideHook.
+   */
+  #[Hook('custom_hook_override', order: Order::First)]
+  public static function customHookOverride(): void {
+    // This normally would run second.
+    // We override that order here with Order::First.
+    // We override, that order in hook_order_first_alphabetically with
+    // OverrideHook.
+    if (!isset($GLOBALS['HookRanTestingOverrideHookFirstAlpha'])) {
+      $GLOBALS['HookOutOfOrderTestingOverrideHook'] = 'HookOutOfOrderTestingOverrideHook';
+    }
+    $GLOBALS['HookRanTestingOverrideHookSecondAlpha'] = 'HookRanTestingOverrideHookSecondAlpha';
+  }
+
+}
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
index 1c7137863cd2..3ea922ec36ce 100644
--- 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
@@ -24,7 +24,11 @@ public static function hookDoNotRun(): void {
    * This hook should run and prevent custom_hook1.
    */
   #[Hook('custom_hook2')]
-  #[RemoveHook('custom_hook1', self::class, 'hook_test_remove', 'hookDoNotRun')]
+  #[RemoveHook(
+    'custom_hook1',
+    class: TestHookRemove::class,
+    method: 'hookDoNotRun'
+  )]
   public static function hookDoRun(): void {
     $GLOBALS['HookShouldRunTestRemove'] = 'HookShouldRunTestRemove';
   }
diff --git a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
index 29a5984545a7..d74c8d8e2aa2 100644
--- a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
@@ -250,9 +250,9 @@ public function testHookLast(): void {
   }
 
   /**
-   * Tests hook replacements.
+   * Tests hook remove.
    */
-  public function testHookReplacements(): void {
+  public function testHookRemove(): void {
     $module_installer = $this->container->get('module_installer');
     $this->assertTrue($module_installer->install(['hook_test_remove']));
     $this->assertFalse(isset($GLOBALS['HookShouldRunTestRemove']));
@@ -265,4 +265,22 @@ public function testHookReplacements(): void {
     $this->assertFalse(isset($GLOBALS['HookShouldNotRunTestRemove']));
   }
 
+  /**
+   * Tests hook override.
+   */
+  public function testHookOverride(): void {
+    $module_installer = $this->container->get('module_installer');
+    $this->assertTrue($module_installer->install(['hook_order_first_alphabetically']));
+    $this->assertTrue($module_installer->install(['hook_order_last_alphabetically']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingOverrideHookFirstAlpha']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingOverrideHook']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingOverrideHookSecondAlpha']));
+    $module_handler = $this->container->get('module_handler');
+    $data = ['hi'];
+    $module_handler->invokeAll('custom_hook_override', $data);
+    $this->assertTrue(isset($GLOBALS['HookRanTestingOverrideHookFirstAlpha']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingOverrideHook']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingOverrideHookSecondAlpha']));
+  }
+
 }
-- 
GitLab


From 80d5446e7a0f9063a85ae8929f6a0f17eede446f Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Sat, 4 Jan 2025 00:26:03 -0500
Subject: [PATCH 099/173] Stan

---
 core/modules/navigation/src/Hook/NavigationHooks.php  | 2 +-
 core/modules/workspaces/src/Hook/EntityOperations.php | 1 -
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/core/modules/navigation/src/Hook/NavigationHooks.php b/core/modules/navigation/src/Hook/NavigationHooks.php
index ee30220ae773..da19ec2dc768 100644
--- a/core/modules/navigation/src/Hook/NavigationHooks.php
+++ b/core/modules/navigation/src/Hook/NavigationHooks.php
@@ -58,7 +58,7 @@ public function __construct(
    * Implements hook_help().
    */
   #[Hook('help')]
-  #[RemoveHook('help', LayoutBuilderHooks::class, 'layout_builder', 'help')]
+  #[RemoveHook('help', class: LayoutBuilderHooks::class, method: 'help')]
   public function help($route_name, RouteMatchInterface $route_match): ?string {
     switch ($route_name) {
       case 'help.page.navigation':
diff --git a/core/modules/workspaces/src/Hook/EntityOperations.php b/core/modules/workspaces/src/Hook/EntityOperations.php
index e0dfe0a15b0e..4240a4daea7e 100644
--- a/core/modules/workspaces/src/Hook/EntityOperations.php
+++ b/core/modules/workspaces/src/Hook/EntityOperations.php
@@ -79,7 +79,6 @@ public function entityPreload(array $ids, string $entity_type_id): array {
   #[Hook('entity_presave', order: Order::First)]
   #[OverrideHook('entity_presave',
     class: ContentModerationHooks::class,
-    module: 'content_moderation',
     method: 'entityPresave',
     order: new OrderBefore(['workspaces'])
   )]
-- 
GitLab


From 956855f7f7e2a6faf4237ce895b1eab6b268a943 Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Sat, 4 Jan 2025 09:19:01 -0500
Subject: [PATCH 100/173] Rename OverrideHook

---
 .../{OverrideHook.php => ReOrderHook.php}     |  7 ++--
 .../Drupal/Core/Hook/Attribute/RemoveHook.php |  2 +-
 .../Drupal/Core/Hook/HookCollectorPass.php    | 33 ++++++++-----------
 .../src/Hook/TestHookOverrideHookFirst.php    | 18 +++++-----
 .../src/Hook/TestHookOverrideHookSecond.php   | 12 +++----
 .../workspaces/src/Hook/EntityOperations.php  |  4 +--
 .../Core/Hook/HookCollectorPassTest.php       | 12 +++----
 7 files changed, 40 insertions(+), 48 deletions(-)
 rename core/lib/Drupal/Core/Hook/Attribute/{OverrideHook.php => ReOrderHook.php} (81%)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/OverrideHook.php b/core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php
similarity index 81%
rename from core/lib/Drupal/Core/Hook/Attribute/OverrideHook.php
rename to core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php
index ebce073a46f0..c7483819b828 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/OverrideHook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php
@@ -8,13 +8,10 @@
 use Drupal\Core\Hook\Order;
 
 /**
- * Attribute for overriding the order of another Hook.
- *
- * When another hook needs to be ordered provide an OverrideHook attribute
- * that specifies the new ordering attribute.
+ * Set the order of an already existing implementation.
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
-class OverrideHook extends Hook {
+class ReOrderHook extends Hook {
 
   /**
    * Constructs a Hook attribute object.
diff --git a/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php b/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
index 1ba132742a87..a911cc29e5df 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
@@ -5,7 +5,7 @@
 namespace Drupal\Core\Hook\Attribute;
 
 /**
- * Attribute for removing another implementation.
+ * Attribute for removing an implementation.
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
 class RemoveHook extends Hook {
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 9aca95cacebb..ba30aa953b10 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -10,7 +10,7 @@
 use Drupal\Core\Extension\ProceduralCall;
 use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\Core\Hook\Attribute\LegacyHook;
-use Drupal\Core\Hook\Attribute\OverrideHook;
+use Drupal\Core\Hook\Attribute\ReOrderHook;
 use Drupal\Core\Hook\Attribute\RemoveHook;
 use Drupal\Core\Hook\Attribute\StopProceduralHookScan;
 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
@@ -75,27 +75,20 @@ public function process(ContainerBuilder $container): array {
     $orderGroups = [];
     $orderAttributes = [];
     $moduleFinder = [];
-    /** @var \Drupal\Core\Hook\Attribute\RemoveHook[] $removals */
-    $removals = [];
-    /** @var \Drupal\Core\Hook\Attribute\OverrideHook[] $overrides */
-    $overrides = [];
+    // These need to be processed after normal hooks.
+    $process_after = [
+      RemoveHook::class => [],
+      ReOrderHook::class => [],
+    ];
     foreach (array_keys($container->getParameter('container.modules')) as $module) {
       foreach ($collector->moduleHooks[$module] ?? [] as $class => $methods) {
         foreach ($methods as $method => $hooks) {
           foreach ($hooks as $hook) {
             assert($hook instanceof Hook);
-            if ($hook instanceof RemoveHook) {
-              $removals[] = $hook;
-              continue;
-            }
-            if ($hook instanceof OverrideHook) {
-              // Gather overrides to ensure these can run last.
-              $overrides[] = $hook;
+            if (isset($process_after[get_class($hook)])) {
+              $process_after[get_class($hook)][] = $hook;
               continue;
             }
-            if ($hook->order) {
-              $this->gatherOrderInformation($hook, $orderAttributes, $orderGroups);
-            }
             if ($class !== ProceduralCall::class) {
               self::checkForProceduralOnlyHooks($hook);
             }
@@ -103,23 +96,25 @@ public function process(ContainerBuilder $container): array {
             $legacyImplementations[$hook->hook][$hook->module] = '';
             $implementations[$hook->hook][$hook->module][$class][$hook->method] = $hook->method;
             $moduleFinder[$class][$hook->method] = $hook->module;
+            if ($hook->order) {
+              $this->gatherOrderInformation($hook, $orderAttributes, $orderGroups);
+            }
           }
         }
       }
     }
-    $orderGroups = array_map('array_unique', $orderGroups);
 
-    foreach ($removals as $hook) {
+    foreach ($process_after[RemoveHook::class] as $hook) {
       if ($module = ($moduleFinder[$hook->class][$hook->method] ?? '')) {
         unset($legacyImplementations[$hook->hook][$module]);
         unset($implementations[$hook->hook][$module][$hook->class][$hook->method]);
       }
     }
 
-    foreach ($overrides as $hook) {
-      // Append overrides last.
+    foreach ($process_after[ReOrderHook::class] as $hook) {
       $this->gatherOrderInformation($hook, $orderAttributes, $orderGroups);
     }
+    $orderGroups = array_map('array_unique', $orderGroups);
 
     // @todo investigate whether this if() is needed after ModuleHandler::add()
     // is removed.
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOverrideHookFirst.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOverrideHookFirst.php
index 5fc5d6ae056c..0c73d1e15975 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOverrideHookFirst.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOverrideHookFirst.php
@@ -6,8 +6,8 @@
 
 use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\Core\Hook\OrderAfter;
-use Drupal\Core\Hook\Attribute\OverrideHook;
-use Drupal\hook_order_last_alphabetically\Hook\TestHookOverrideHookSecond;
+use Drupal\Core\Hook\Attribute\ReOrderHook;
+use Drupal\hook_order_last_alphabetically\Hook\TestHookReOrderHookSecond;
 
 /**
  * Hook implementations for verifying ordering hooks by attributes.
@@ -21,25 +21,25 @@
  *
  * Each method pair tests one hook ordering permutation.
  */
-class TestHookOverrideHookFirst {
+class TestHookReOrderHookFirst {
 
   /**
-   * This pair tests OverrideHook.
+   * This pair tests ReOrderHook.
    */
   #[Hook('custom_hook_override')]
-  #[OverrideHook(
+  #[ReOrderHook(
     'custom_hook_override',
-    class: TestHookOverrideHookSecond::class,
+    class: TestHookReOrderHookSecond::class,
     method: 'customHookOverride',
     order: new OrderAfter(
-      classesAndMethods: [[TestHookOverrideHookFirst::class, 'customHookOverride']],
+      classesAndMethods: [[TestHookReOrderHookFirst::class, 'customHookOverride']],
     )
   )]
   public static function customHookOverride(): void {
     // This normally would run first.
     // We override that order in hook_order_second_alphabetically.
-    // We override, that order here with OverrideHook.
-    $GLOBALS['HookRanTestingOverrideHookFirstAlpha'] = 'HookRanTestingOverrideHookFirstAlpha';
+    // We override, that order here with ReOrderHook.
+    $GLOBALS['HookRanTestingReOrderHookFirstAlpha'] = 'HookRanTestingReOrderHookFirstAlpha';
   }
 
 }
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOverrideHookSecond.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOverrideHookSecond.php
index bb7d26a6c311..b649e5408497 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOverrideHookSecond.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOverrideHookSecond.php
@@ -19,21 +19,21 @@
  *
  * Each method pair tests one hook ordering permutation.
  */
-class TestHookOverrideHookSecond {
+class TestHookReOrderHookSecond {
 
   /**
-   * This pair tests OverrideHook.
+   * This pair tests ReOrderHook.
    */
   #[Hook('custom_hook_override', order: Order::First)]
   public static function customHookOverride(): void {
     // This normally would run second.
     // We override that order here with Order::First.
     // We override, that order in hook_order_first_alphabetically with
-    // OverrideHook.
-    if (!isset($GLOBALS['HookRanTestingOverrideHookFirstAlpha'])) {
-      $GLOBALS['HookOutOfOrderTestingOverrideHook'] = 'HookOutOfOrderTestingOverrideHook';
+    // ReOrderHook.
+    if (!isset($GLOBALS['HookRanTestingReOrderHookFirstAlpha'])) {
+      $GLOBALS['HookOutOfOrderTestingReOrderHook'] = 'HookOutOfOrderTestingReOrderHook';
     }
-    $GLOBALS['HookRanTestingOverrideHookSecondAlpha'] = 'HookRanTestingOverrideHookSecondAlpha';
+    $GLOBALS['HookRanTestingReOrderHookSecondAlpha'] = 'HookRanTestingReOrderHookSecondAlpha';
   }
 
 }
diff --git a/core/modules/workspaces/src/Hook/EntityOperations.php b/core/modules/workspaces/src/Hook/EntityOperations.php
index 4240a4daea7e..915499f685eb 100644
--- a/core/modules/workspaces/src/Hook/EntityOperations.php
+++ b/core/modules/workspaces/src/Hook/EntityOperations.php
@@ -12,7 +12,7 @@
 use Drupal\Core\Entity\RevisionableInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\Attribute\OverrideHook;
+use Drupal\Core\Hook\Attribute\ReOrderHook;
 use Drupal\Core\Hook\Order;
 use Drupal\Core\Hook\OrderBefore;
 use Drupal\content_moderation\Hook\ContentModerationHooks;
@@ -77,7 +77,7 @@ public function entityPreload(array $ids, string $entity_type_id): array {
    * Implements hook_entity_presave().
    */
   #[Hook('entity_presave', order: Order::First)]
-  #[OverrideHook('entity_presave',
+  #[ReOrderHook('entity_presave',
     class: ContentModerationHooks::class,
     method: 'entityPresave',
     order: new OrderBefore(['workspaces'])
diff --git a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
index d74c8d8e2aa2..333db179a831 100644
--- a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
@@ -272,15 +272,15 @@ public function testHookOverride(): void {
     $module_installer = $this->container->get('module_installer');
     $this->assertTrue($module_installer->install(['hook_order_first_alphabetically']));
     $this->assertTrue($module_installer->install(['hook_order_last_alphabetically']));
-    $this->assertFalse(isset($GLOBALS['HookRanTestingOverrideHookFirstAlpha']));
-    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingOverrideHook']));
-    $this->assertFalse(isset($GLOBALS['HookRanTestingOverrideHookSecondAlpha']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingReOrderHookFirstAlpha']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingReOrderHook']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingReOrderHookSecondAlpha']));
     $module_handler = $this->container->get('module_handler');
     $data = ['hi'];
     $module_handler->invokeAll('custom_hook_override', $data);
-    $this->assertTrue(isset($GLOBALS['HookRanTestingOverrideHookFirstAlpha']));
-    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingOverrideHook']));
-    $this->assertTrue(isset($GLOBALS['HookRanTestingOverrideHookSecondAlpha']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingReOrderHookFirstAlpha']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingReOrderHook']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingReOrderHookSecondAlpha']));
   }
 
 }
-- 
GitLab


From 4837f034d6d905dbfc77afd8f83b0b50030abdb6 Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Sat, 4 Jan 2025 09:28:10 -0500
Subject: [PATCH 101/173] Rename classes

---
 ...HookOverrideHookFirst.php => TestHookReOrderHookFirst.php} | 4 ++--
 ...HookOverrideHookSecond.php => TestHookReOrderHookLast.php} | 4 ++--
 .../Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php    | 4 ++--
 3 files changed, 6 insertions(+), 6 deletions(-)
 rename core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/{TestHookOverrideHookFirst.php => TestHookReOrderHookFirst.php} (96%)
 rename core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/{TestHookOverrideHookSecond.php => TestHookReOrderHookLast.php} (89%)

diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOverrideHookFirst.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookReOrderHookFirst.php
similarity index 96%
rename from core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOverrideHookFirst.php
rename to core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookReOrderHookFirst.php
index 0c73d1e15975..8e48435ebae1 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOverrideHookFirst.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookReOrderHookFirst.php
@@ -7,7 +7,7 @@
 use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\Core\Hook\OrderAfter;
 use Drupal\Core\Hook\Attribute\ReOrderHook;
-use Drupal\hook_order_last_alphabetically\Hook\TestHookReOrderHookSecond;
+use Drupal\hook_order_last_alphabetically\Hook\TestHookReOrderHookLast;
 
 /**
  * Hook implementations for verifying ordering hooks by attributes.
@@ -29,7 +29,7 @@ class TestHookReOrderHookFirst {
   #[Hook('custom_hook_override')]
   #[ReOrderHook(
     'custom_hook_override',
-    class: TestHookReOrderHookSecond::class,
+    class: TestHookReOrderHookLast::class,
     method: 'customHookOverride',
     order: new OrderAfter(
       classesAndMethods: [[TestHookReOrderHookFirst::class, 'customHookOverride']],
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOverrideHookSecond.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookReOrderHookLast.php
similarity index 89%
rename from core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOverrideHookSecond.php
rename to core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookReOrderHookLast.php
index b649e5408497..a46e3583609e 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOverrideHookSecond.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookReOrderHookLast.php
@@ -19,7 +19,7 @@
  *
  * Each method pair tests one hook ordering permutation.
  */
-class TestHookReOrderHookSecond {
+class TestHookReOrderHookLast {
 
   /**
    * This pair tests ReOrderHook.
@@ -33,7 +33,7 @@ public static function customHookOverride(): void {
     if (!isset($GLOBALS['HookRanTestingReOrderHookFirstAlpha'])) {
       $GLOBALS['HookOutOfOrderTestingReOrderHook'] = 'HookOutOfOrderTestingReOrderHook';
     }
-    $GLOBALS['HookRanTestingReOrderHookSecondAlpha'] = 'HookRanTestingReOrderHookSecondAlpha';
+    $GLOBALS['HookRanTestingReOrderHookLastAlpha'] = 'HookRanTestingReOrderHookLastAlpha';
   }
 
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
index 333db179a831..38740b09d818 100644
--- a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
@@ -274,13 +274,13 @@ public function testHookOverride(): void {
     $this->assertTrue($module_installer->install(['hook_order_last_alphabetically']));
     $this->assertFalse(isset($GLOBALS['HookRanTestingReOrderHookFirstAlpha']));
     $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingReOrderHook']));
-    $this->assertFalse(isset($GLOBALS['HookRanTestingReOrderHookSecondAlpha']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingReOrderHookLastAlpha']));
     $module_handler = $this->container->get('module_handler');
     $data = ['hi'];
     $module_handler->invokeAll('custom_hook_override', $data);
     $this->assertTrue(isset($GLOBALS['HookRanTestingReOrderHookFirstAlpha']));
     $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingReOrderHook']));
-    $this->assertTrue(isset($GLOBALS['HookRanTestingReOrderHookSecondAlpha']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingReOrderHookLastAlpha']));
   }
 
 }
-- 
GitLab


From 3523a553d9d8323f63887aba04fd2a93632eaba1 Mon Sep 17 00:00:00 2001
From: Ghost Of Drupal Past <drupal@negyesi.net>
Date: Mon, 10 Feb 2025 15:19:32 +0100
Subject: [PATCH 102/173] add HookPriority unit tests

---
 core/lib/Drupal/Core/Hook/HookPriority.php    |   4 +-
 .../Hook/HookPriorityEqualPriorityTest.php    |  76 +++++++++
 .../Tests/Core/Hook/HookPriorityTest.php      | 148 ++++++++++++++++++
 .../Tests/Core/Hook/HookPriorityTestBase.php  |  84 ++++++++++
 4 files changed, 311 insertions(+), 1 deletion(-)
 create mode 100644 core/tests/Drupal/Tests/Core/Hook/HookPriorityEqualPriorityTest.php
 create mode 100644 core/tests/Drupal/Tests/Core/Hook/HookPriorityTest.php
 create mode 100644 core/tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php

diff --git a/core/lib/Drupal/Core/Hook/HookPriority.php b/core/lib/Drupal/Core/Hook/HookPriority.php
index 9a9fe4e6321a..1fc4df0abd55 100644
--- a/core/lib/Drupal/Core/Hook/HookPriority.php
+++ b/core/lib/Drupal/Core/Hook/HookPriority.php
@@ -22,7 +22,9 @@ public function __construct(protected ContainerBuilder $container) {}
    * @param string $event
    *   Listeners to this event will be ordered.
    * @param \Drupal\Core\Hook\Attribute\Hook $hook
-   *   The hook attribute.
+   *   The hook attribute. Most of the order parameter is ignored by this
+   *   class, only $hook->order->value is used. The rest is preprocessed by
+   *   HookCollectorPass and passed in $other_specifiers.
    * @param array|null $other_specifiers
    *   Other hook implementations to compare to, if any. The array is a list of
    *   strings, each string is a class and method separated by ::.
diff --git a/core/tests/Drupal/Tests/Core/Hook/HookPriorityEqualPriorityTest.php b/core/tests/Drupal/Tests/Core/Hook/HookPriorityEqualPriorityTest.php
new file mode 100644
index 000000000000..644f53ee8c6b
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Hook/HookPriorityEqualPriorityTest.php
@@ -0,0 +1,76 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\Core\Hook;
+
+use Drupal\Core\Hook\Order;
+use Drupal\Core\Hook\OrderAfter;
+use Drupal\Core\Hook\OrderBefore;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Hook\HookPriority
+ *
+ * @group Hook
+ */
+class HookPriorityEqualPriorityTest extends HookPriorityTestBase {
+
+  protected function setUp(): void {
+    parent::setUp();
+    // The priority of "a", "b", "c" are the same, the order is undefined.
+    $this->setUpContainer(FALSE);
+    $this->assertSame($this->getPriority('a'), $this->getPriority('b'));
+    $this->assertSame($this->getPriority('b'), $this->getPriority('c'));
+  }
+
+  /**
+   * @covers ::first
+   */
+  public function testFirst(): void {
+    // "c" was first, make "a" the first.
+    $this->doPriorityChange('a', Order::First);
+    $this->assertGreaterThan($this->getPriority('c'), $this->getPriority('a'));
+    $this->assertGreaterThan($this->getPriority('b'), $this->getPriority('a'));
+    // Nothing else can be asserted: by setting the same priority, the setup
+    // had undefined order and so the services not included in the helper call
+    // can be in any order.
+  }
+
+  /**
+   * @covers ::last
+   */
+  public function testLast(): void {
+    // "c" was first, make it the last.
+    $this->doPriorityChange('c', Order::Last);
+    $this->assertGreaterThan($this->getPriority('c'), $this->getPriority('a'));
+    $this->assertGreaterThan($this->getPriority('c'), $this->getPriority('b'));
+    // Nothing else can be asserted: by setting the same priority, the setup
+    // had undefined order and so the services not included in the helper call
+    // can be in any order.
+  }
+
+  /**
+   * @covers ::before
+   */
+  public function testBefore(): void {
+    // "a" was last, move it before "b".
+    $this->doPriorityChange('a', OrderBefore::class, 'b');
+    $this->assertGreaterThan($this->getPriority('b'), $this->getPriority('a'));
+    // Nothing else can be asserted: by setting the same priority, the setup
+    // had undefined order and so the services not included in the helper call
+    // can be in any order.
+  }
+
+  /**
+   * @covers ::after
+   */
+  public function testAfter(): void {
+    // "c" was first, move it after "b".
+    $this->doPriorityChange('c', OrderAfter::class, 'b');
+    $this->assertGreaterThan($this->getPriority('c'), $this->getPriority('b'));
+    // Nothing else can be asserted: by setting the same priority, the setup
+    // had undefined order and so the services not included in the helper call
+    // can be in any order.
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Hook/HookPriorityTest.php b/core/tests/Drupal/Tests/Core/Hook/HookPriorityTest.php
new file mode 100644
index 000000000000..d8fb81927be0
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Hook/HookPriorityTest.php
@@ -0,0 +1,148 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\Core\Hook;
+
+use Drupal\Core\Hook\Order;
+use Drupal\Core\Hook\OrderAfter;
+use Drupal\Core\Hook\OrderBefore;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Hook\HookPriority
+ *
+ * @group Hook
+ */
+class HookPriorityTest extends HookPriorityTestBase {
+
+  /**
+   * The original priorities.
+   *
+   * @var array
+   */
+  protected array $originalPriorities = [];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    // "c" first, "b" second, "a" last.
+    $this->setUpContainer(TRUE);
+    foreach (['a', 'b', 'c'] as $key) {
+      $this->originalPriorities[$key] = $this->getPriority($key);
+    }
+    // The documentation does not clarify the order of arguments, let's do so
+    // here to make it easier to develop/debug this test.
+    $this->assertGreaterThan(1, 2);
+    // According to https://symfony.com/doc/current/event_dispatcher.html
+    // the higher the number, the earlier a listener is executed. Accordingly
+    // assert that "a" is last, "c" is first, "b" is in the middle. The
+    // asserts in methods can be compared to these establishing asserts.
+    $this->assertGreaterThan($this->getPriority('a'), $this->getPriority('b'));
+    $this->assertGreaterThan($this->getPriority('b'), $this->getPriority('c'));
+    // This is unnecessary, but it's easier to compare if this is explicit.
+    $this->assertGreaterThan($this->getPriority('a'), $this->getPriority('c'));
+  }
+
+  /**
+   * @covers ::first
+   */
+  public function testFirst(): void {
+    // "c" was first, make "a" the first.
+    $this->doPriorityChange('a', Order::First);
+    $this->assertGreaterThan($this->getPriority('c'), $this->getPriority('a'));
+    $this->assertGreaterThan($this->getPriority('b'), $this->getPriority('a'));
+    // The other two shouldn't change.
+    $this->assertNoChange('a');
+  }
+
+  /**
+   * @covers ::last
+   */
+  public function testLast(): void {
+    // "c" was first, make it the last.
+    $this->doPriorityChange('c', Order::Last);
+    $this->assertGreaterThan($this->getPriority('c'), $this->getPriority('a'));
+    $this->assertGreaterThan($this->getPriority('c'), $this->getPriority('b'));
+    // The other two shouldn't change.
+    $this->assertNoChange('c');
+  }
+
+  /**
+   * @covers ::before
+   */
+  public function testBefore(): void {
+    // "a" was last, move it before "b".
+    $this->doPriorityChange('a', OrderBefore::class, 'b');
+    $this->assertGreaterThan($this->getPriority('b'), $this->getPriority('a'));
+    // The relation between these should not change. The actual priority
+    // might.
+    $this->assertGreaterThan($this->getPriority('b'), $this->getPriority('c'));
+    $this->assertGreaterThan($this->getPriority('a'), $this->getPriority('c'));
+  }
+
+  /**
+   * @covers ::after
+   */
+  public function testAfter(): void {
+    // "c" was first, move it after "b".
+    $this->doPriorityChange('c', OrderAfter::class, 'b');
+    $this->assertGreaterThan($this->getPriority('c'), $this->getPriority('b'));
+    // The relation between these should not change. The actual priority
+    // might.
+    $this->assertGreaterThan($this->getPriority('a'), $this->getPriority('b'));
+    $this->assertGreaterThan($this->getPriority('a'), $this->getPriority('c'));
+  }
+
+  /**
+   * @covers ::first
+   */
+  public function testFirstNoChange(): void {
+    // "c" was first, making it first should be a no-op.
+    $this->doPriorityChange('c', Order::First);
+    $this->assertNoChange();
+  }
+
+  /**
+   * @covers ::last
+   */
+  public function testLastNoChange(): void {
+    // "a" was last, making it last should be a no-op.
+    $this->doPriorityChange('a', Order::Last);
+    $this->assertNoChange();
+  }
+
+  /**
+   * @covers ::before
+   */
+  public function testBeforeNoChange(): void {
+    // "b" is already firing before "a", this should be a no-op.
+    $this->doPriorityChange('b', OrderBefore::class, 'a');
+    $this->assertNoChange();
+  }
+
+  /**
+   * @covers ::after
+   */
+  public function testAfterNoChange(): void {
+    // "b' is already firing after "c", this should be a no-op.
+    $this->doPriorityChange('b', OrderAfter::class, 'c');
+    $this->assertNoChange();
+  }
+
+  /**
+   * Asserts no change has happened.
+   *
+   * @param string $changed
+   *   This one did change. Assert the rest did not change.
+   */
+  protected function assertNoChange(string $changed = ''): void {
+    foreach ($this->originalPriorities as $key => $priority) {
+      if ($key !== $changed) {
+        $this->assertSame($priority, $this->getPriority($key));
+      }
+    }
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php b/core/tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php
new file mode 100644
index 000000000000..7e47c4ac8ec0
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php
@@ -0,0 +1,84 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\Core\Hook;
+
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\Hook\HookPriority;
+use Drupal\Core\Hook\Order;
+use Drupal\Tests\UnitTestCase;
+
+abstract class HookPriorityTestBase extends UnitTestCase {
+
+  /**
+   * The container builder.
+   *
+   * @var \Drupal\Core\DependencyInjection\ContainerBuilder
+   */
+  protected ContainerBuilder $container;
+
+  /**
+   * Set up three service listeners, "a", "b" and "c".
+   *
+   * The service id, the class name and the method name are all the same.
+   *
+   * @param bool $different_priority
+   *   When TRUE, "c" will fire first, "b" second and "a" last. When FALSE,
+   *   the priority will be set to be the same and the order is undefined.
+   *
+   * @return void
+   */
+  protected function setUpContainer(bool $different_priority): void {
+    $this->container = new ContainerBuilder();
+    foreach (['a', 'b', 'c'] as $key => $name) {
+      $definition = $this->container
+        ->register($name, $name)
+        ->setAutowired(TRUE);
+      $definition->addTag('kernel.event_listener', [
+        'event' => 'drupal_hook.test',
+        'method' => $name,
+        // Do not use $key itself to avoid a 0 priority which could potentially
+        // lead to misleading results.
+        'priority' => $different_priority ? $key + 3 : 0,
+      ]);
+    }
+  }
+
+  /**
+   * Get the priority for a service.
+   */
+  protected function getPriority(string $name): int {
+    $definition = $this->container->getDefinition($name);
+    return $definition->getTags()['kernel.event_listener'][0]['priority'];
+  }
+
+  /**
+   * Change priority of a class and method.
+   *
+   * @param class-string $classBeingChanged
+   *   The class being changed, the method has the same name.
+   * @param Order|class-string $order
+   *  Either a member of the Order enum or the name of a ComplexOrder class.
+   * @param class-string $relativeTo
+   *   If the operation is before or after, this is the name of the class
+   *   the operation changes relative to.
+   */
+  protected function doPriorityChange(string $classBeingChanged, Order|string $order, string $relativeTo = ''): void {
+    if ($relativeTo) {
+      // The modules / classesAndMethods argument of the order class is
+      // processed in HookCollectorPass and is ignored by HookPriority, they
+      // are passed to HookPriority in the $other_specifiers argument.
+      $hook = new Hook('test', order: new $order(modules: ['']));
+      $other_specifiers = ["$relativeTo::$relativeTo"];
+    }
+    else {
+      $hook = new Hook('test', order: $order);
+      $other_specifiers = NULL;
+    }
+    $hook->set(class: $classBeingChanged, module: '', method: $classBeingChanged);
+    (new HookPriority($this->container))->change('drupal_hook.test', $hook, $other_specifiers);
+  }
+
+}
-- 
GitLab


From 1671deadcf98d61211069e2417029535a54fbc42 Mon Sep 17 00:00:00 2001
From: Ghost Of Drupal Past <drupal@negyesi.net>
Date: Mon, 10 Feb 2025 16:28:26 +0100
Subject: [PATCH 103/173] ah it doesnt cover anything any more

---
 .../Hook/HookPriorityEqualPriorityTest.php    | 12 ----------
 .../Tests/Core/Hook/HookPriorityTest.php      | 24 -------------------
 .../Tests/Core/Hook/HookPriorityTestBase.php  |  4 ++--
 3 files changed, 2 insertions(+), 38 deletions(-)

diff --git a/core/tests/Drupal/Tests/Core/Hook/HookPriorityEqualPriorityTest.php b/core/tests/Drupal/Tests/Core/Hook/HookPriorityEqualPriorityTest.php
index 644f53ee8c6b..985e3d55c997 100644
--- a/core/tests/Drupal/Tests/Core/Hook/HookPriorityEqualPriorityTest.php
+++ b/core/tests/Drupal/Tests/Core/Hook/HookPriorityEqualPriorityTest.php
@@ -23,9 +23,6 @@ protected function setUp(): void {
     $this->assertSame($this->getPriority('b'), $this->getPriority('c'));
   }
 
-  /**
-   * @covers ::first
-   */
   public function testFirst(): void {
     // "c" was first, make "a" the first.
     $this->doPriorityChange('a', Order::First);
@@ -36,9 +33,6 @@ public function testFirst(): void {
     // can be in any order.
   }
 
-  /**
-   * @covers ::last
-   */
   public function testLast(): void {
     // "c" was first, make it the last.
     $this->doPriorityChange('c', Order::Last);
@@ -49,9 +43,6 @@ public function testLast(): void {
     // can be in any order.
   }
 
-  /**
-   * @covers ::before
-   */
   public function testBefore(): void {
     // "a" was last, move it before "b".
     $this->doPriorityChange('a', OrderBefore::class, 'b');
@@ -61,9 +52,6 @@ public function testBefore(): void {
     // can be in any order.
   }
 
-  /**
-   * @covers ::after
-   */
   public function testAfter(): void {
     // "c" was first, move it after "b".
     $this->doPriorityChange('c', OrderAfter::class, 'b');
diff --git a/core/tests/Drupal/Tests/Core/Hook/HookPriorityTest.php b/core/tests/Drupal/Tests/Core/Hook/HookPriorityTest.php
index d8fb81927be0..65c8193b8f83 100644
--- a/core/tests/Drupal/Tests/Core/Hook/HookPriorityTest.php
+++ b/core/tests/Drupal/Tests/Core/Hook/HookPriorityTest.php
@@ -45,9 +45,6 @@ protected function setUp(): void {
     $this->assertGreaterThan($this->getPriority('a'), $this->getPriority('c'));
   }
 
-  /**
-   * @covers ::first
-   */
   public function testFirst(): void {
     // "c" was first, make "a" the first.
     $this->doPriorityChange('a', Order::First);
@@ -57,9 +54,6 @@ public function testFirst(): void {
     $this->assertNoChange('a');
   }
 
-  /**
-   * @covers ::last
-   */
   public function testLast(): void {
     // "c" was first, make it the last.
     $this->doPriorityChange('c', Order::Last);
@@ -69,9 +63,6 @@ public function testLast(): void {
     $this->assertNoChange('c');
   }
 
-  /**
-   * @covers ::before
-   */
   public function testBefore(): void {
     // "a" was last, move it before "b".
     $this->doPriorityChange('a', OrderBefore::class, 'b');
@@ -82,9 +73,6 @@ public function testBefore(): void {
     $this->assertGreaterThan($this->getPriority('a'), $this->getPriority('c'));
   }
 
-  /**
-   * @covers ::after
-   */
   public function testAfter(): void {
     // "c" was first, move it after "b".
     $this->doPriorityChange('c', OrderAfter::class, 'b');
@@ -95,36 +83,24 @@ public function testAfter(): void {
     $this->assertGreaterThan($this->getPriority('a'), $this->getPriority('c'));
   }
 
-  /**
-   * @covers ::first
-   */
   public function testFirstNoChange(): void {
     // "c" was first, making it first should be a no-op.
     $this->doPriorityChange('c', Order::First);
     $this->assertNoChange();
   }
 
-  /**
-   * @covers ::last
-   */
   public function testLastNoChange(): void {
     // "a" was last, making it last should be a no-op.
     $this->doPriorityChange('a', Order::Last);
     $this->assertNoChange();
   }
 
-  /**
-   * @covers ::before
-   */
   public function testBeforeNoChange(): void {
     // "b" is already firing before "a", this should be a no-op.
     $this->doPriorityChange('b', OrderBefore::class, 'a');
     $this->assertNoChange();
   }
 
-  /**
-   * @covers ::after
-   */
   public function testAfterNoChange(): void {
     // "b' is already firing after "c", this should be a no-op.
     $this->doPriorityChange('b', OrderAfter::class, 'c');
diff --git a/core/tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php b/core/tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php
index 7e47c4ac8ec0..64468cbf1ae1 100644
--- a/core/tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php
+++ b/core/tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php
@@ -59,8 +59,8 @@ protected function getPriority(string $name): int {
    *
    * @param class-string $classBeingChanged
    *   The class being changed, the method has the same name.
-   * @param Order|class-string $order
-   *  Either a member of the Order enum or the name of a ComplexOrder class.
+   * @param \Drupal\Core\Hook\Order|class-string $order
+   *   Either a member of the Order enum or the name of a ComplexOrder class.
    * @param class-string $relativeTo
    *   If the operation is before or after, this is the name of the class
    *   the operation changes relative to.
-- 
GitLab


From 596829d597427ab4bd22291a955547c44f6b5a9c Mon Sep 17 00:00:00 2001
From: nicxvan <29861-nicxvan@users.noreply.drupalcode.org>
Date: Mon, 10 Feb 2025 15:50:53 +0000
Subject: [PATCH 104/173] class-string

---
 core/lib/Drupal/Core/Hook/Attribute/Hook.php        | 2 +-
 core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php | 2 +-
 core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php  | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
index a48e3a52c289..158e99fc7abf 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -133,7 +133,7 @@ public function __construct(
   /**
    * Set necessary parameters for the hook attribute.
    *
-   * @param string $class
+   * @param class-string $class
    *   The class for the hook.
    * @param string $module
    *   The module for the hook.
diff --git a/core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php b/core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php
index c7483819b828..ec2033dc8718 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php
@@ -18,7 +18,7 @@ class ReOrderHook extends Hook {
    *
    * @param string $hook
    *   The short hook name, without the 'hook_' prefix.
-   * @param string $class
+   * @param class-string $class
    *   The class the implementation to modify is in. This allows one module to
    *   affect the order of another module's hook.
    * @param string $method
diff --git a/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php b/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
index a911cc29e5df..4e642ac2b356 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
@@ -15,7 +15,7 @@ class RemoveHook extends Hook {
    *
    * @param string $hook
    *   The short hook name, without the 'hook_' prefix.
-   * @param string $class
+   * @param class-string $class
    *   The class the implementation to remove is in.
    * @param string $method
    *   The method name of the implementation to remove.
-- 
GitLab


From 1ef2e40b885fe41aa3b02efb1891302e5a211ce4 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Mon, 10 Feb 2025 11:47:48 -0500
Subject: [PATCH 105/173] Comment updates

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 25 ++++++++++++++++++-
 1 file changed, 24 insertions(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index ba30aa953b10..cd968d64075a 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -29,6 +29,8 @@
  *
  * 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 {
 
@@ -70,12 +72,20 @@ class HookCollectorPass implements CompilerPassInterface {
    */
   public function process(ContainerBuilder $container): array {
     $collector = static::collectAllHookImplementations($container->getParameter('container.modules'), $container);
+    // List of modules implementing hooks with the implementation details.
     $implementations = [];
+    // List of modules implementing hooks, used for
+    // hook_module_implementations_alter.
     $legacyImplementations = [];
+    // Groups of hooks that should be ordered together.
     $orderGroups = [];
+    // Attributes related to ordering hooks.
     $orderAttributes = [];
+    // List of modules that the hooks are defined for, keyed by class and
+    // method.
     $moduleFinder = [];
-    // These need to be processed after normal hooks.
+    // These attributes need to be processed after all hooks have been
+    // processed.
     $process_after = [
       RemoveHook::class => [],
       ReOrderHook::class => [],
@@ -92,9 +102,14 @@ public function process(ContainerBuilder $container): array {
             if ($class !== ProceduralCall::class) {
               self::checkForProceduralOnlyHooks($hook);
             }
+            // Set properties on hook class that are needed for registration.
             $hook->set(... compact('class', 'method', 'module'));
+            // Store a list of modules implementing hooks for simplifying
+            // registration and hook_module_implements_alter execution.
             $legacyImplementations[$hook->hook][$hook->module] = '';
+            // Store the implementation details for registering the hook.
             $implementations[$hook->hook][$hook->module][$class][$hook->method] = $hook->method;
+            // Reverse lookup for modules implementing hooks.
             $moduleFinder[$class][$hook->method] = $hook->module;
             if ($hook->order) {
               $this->gatherOrderInformation($hook, $orderAttributes, $orderGroups);
@@ -104,6 +119,10 @@ public function process(ContainerBuilder $container): array {
       }
     }
 
+    // Loop over all RemoveHook attributes and remove them from the maps before
+    // registering the hooks. This must happen after all collection, but before
+    // registration to ensure the hook it is removing has already been
+    // discovered.
     foreach ($process_after[RemoveHook::class] as $hook) {
       if ($module = ($moduleFinder[$hook->class][$hook->method] ?? '')) {
         unset($legacyImplementations[$hook->hook][$module]);
@@ -111,6 +130,10 @@ public function process(ContainerBuilder $container): array {
       }
     }
 
+    // Loop over all ReOrderHook attributes and remove them from the maps
+    // before registering the hooks. This must happen after all collection,
+    // but before registration to ensure this ordering directive takes
+    // precedence.
     foreach ($process_after[ReOrderHook::class] as $hook) {
       $this->gatherOrderInformation($hook, $orderAttributes, $orderGroups);
     }
-- 
GitLab


From e446e71cfb27ad1531863dfdebf5ed69c4863d71 Mon Sep 17 00:00:00 2001
From: nicxvan <29861-nicxvan@users.noreply.drupalcode.org>
Date: Mon, 10 Feb 2025 17:57:58 +0000
Subject: [PATCH 106/173] Remove internal

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 2 --
 1 file changed, 2 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index cd968d64075a..f7bca9dbbd33 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -29,8 +29,6 @@
  *
  * 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 {
 
-- 
GitLab


From 1f5a97766f1e86acccb8b360da858e2a42c8fae5 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Mon, 10 Feb 2025 14:19:25 -0500
Subject: [PATCH 107/173] Add internal

---
 core/lib/Drupal/Core/Hook/Attribute/Hook.php                   | 2 ++
 core/lib/Drupal/Core/Hook/Attribute/LegacyHook.php             | 2 ++
 core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php            | 2 ++
 core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php             | 2 ++
 core/lib/Drupal/Core/Hook/Attribute/StopProceduralHookScan.php | 2 ++
 5 files changed, 10 insertions(+)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
index 158e99fc7abf..19032791b311 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -95,6 +95,8 @@
  * the procedural hook implementations.
  *
  * See \Drupal\Core\Hook\Attribute\LegacyHook for additional information.
+ *
+ * @internal
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
 class Hook {
diff --git a/core/lib/Drupal/Core/Hook/Attribute/LegacyHook.php b/core/lib/Drupal/Core/Hook/Attribute/LegacyHook.php
index ee6501d7b42e..4b2726a635c2 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/LegacyHook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/LegacyHook.php
@@ -19,6 +19,8 @@
  * only the legacy hook implementation is executed.
  *
  * For more information, see https://www.drupal.org/node/3442349.
+ *
+ * @internal
  */
 #[\Attribute(\Attribute::TARGET_FUNCTION)]
 class LegacyHook {
diff --git a/core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php b/core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php
index ec2033dc8718..98ce8aff5602 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php
@@ -9,6 +9,8 @@
 
 /**
  * Set the order of an already existing implementation.
+ *
+ * @internal
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
 class ReOrderHook extends Hook {
diff --git a/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php b/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
index 4e642ac2b356..153a5c198522 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
@@ -6,6 +6,8 @@
 
 /**
  * Attribute for removing an implementation.
+ *
+ * @internal
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
 class RemoveHook extends Hook {
diff --git a/core/lib/Drupal/Core/Hook/Attribute/StopProceduralHookScan.php b/core/lib/Drupal/Core/Hook/Attribute/StopProceduralHookScan.php
index 73f0ce6915bd..cc41fff51533 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/StopProceduralHookScan.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/StopProceduralHookScan.php
@@ -9,6 +9,8 @@
  *
  * This allows contrib and core to mark when a file has no more
  * procedural hooks.
+ *
+ * @internal
  */
 #[\Attribute(\Attribute::TARGET_FUNCTION)]
 class StopProceduralHookScan {
-- 
GitLab


From 30a7dd1da51cbb27b6f2c5defc1baadfd7570023 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Wed, 12 Feb 2025 10:28:53 -0500
Subject: [PATCH 108/173] Update remove docs

---
 core/lib/Drupal/Core/Hook/Attribute/Hook.php | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
index 19032791b311..3ca1cd13bbeb 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -38,7 +38,8 @@
  *
  * @see https://www.drupal.org/node/3493962
  *
- * Removing hook implementations can be done by using the remove parameter.
+ * Removing hook implementations can be done by using the attribute
+ * \Drupal\Core\Hook\Attribute/RemoveHook.
  *
  * @see https://www.drupal.org/node/3496786
  *
-- 
GitLab


From ca019f8e86c9a657a6e8833abbb3c897e33773d2 Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Sun, 16 Feb 2025 23:28:53 -0500
Subject: [PATCH 109/173] Content translation

---
 .../content_translation/src/Hook/ContentTranslationHooks.php | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/core/modules/content_translation/src/Hook/ContentTranslationHooks.php b/core/modules/content_translation/src/Hook/ContentTranslationHooks.php
index 8c0d0e816f61..089b0a5ffba3 100644
--- a/core/modules/content_translation/src/Hook/ContentTranslationHooks.php
+++ b/core/modules/content_translation/src/Hook/ContentTranslationHooks.php
@@ -190,13 +190,8 @@ public function entityTypeAlter(array &$entity_types) : void {
    * @see content_translation_entity_bundle_info_alter()
    * @see \Drupal\content_translation\ContentTranslationManager::isEnabled()
    */
-<<<<<<< HEAD
-  #[Hook('language_content_settings_insert', order: Order::Last)]
-  public function languageContentSettingsInsert(ContentLanguageSettingsInterface $settings): void {
-=======
   #[Hook('language_content_settings_insert')]
   public function languageContentSettingsInsert(ContentLanguageSettingsInterface $settings): void {
->>>>>>> beef39243fd (Update correct hooks)
     if ($settings->getThirdPartySetting('content_translation', 'enabled', FALSE)) {
       _content_translation_install_field_storage_definitions($settings->getTargetEntityTypeId());
     }
-- 
GitLab


From 32361bc9ba1730fc6ff4a7ed8c97b8111cbdab2a Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Sun, 16 Feb 2025 23:33:38 -0500
Subject: [PATCH 110/173] Code sniffing

---
 core/tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php | 2 --
 1 file changed, 2 deletions(-)

diff --git a/core/tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php b/core/tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php
index 64468cbf1ae1..ddee181e3f62 100644
--- a/core/tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php
+++ b/core/tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php
@@ -27,8 +27,6 @@ abstract class HookPriorityTestBase extends UnitTestCase {
    * @param bool $different_priority
    *   When TRUE, "c" will fire first, "b" second and "a" last. When FALSE,
    *   the priority will be set to be the same and the order is undefined.
-   *
-   * @return void
    */
   protected function setUpContainer(bool $different_priority): void {
     $this->container = new ContainerBuilder();
-- 
GitLab


From eb2e3f542435a58f45cbfc50560c5d139a782338 Mon Sep 17 00:00:00 2001
From: nicxvan <29861-nicxvan@users.noreply.drupalcode.org>
Date: Mon, 24 Feb 2025 04:49:21 +0000
Subject: [PATCH 111/173] Suggestions

---
 core/lib/Drupal/Core/Hook/Attribute/Hook.php    |  6 +++---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 14 +++++++-------
 2 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
index 3ca1cd13bbeb..151f308b5508 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -105,9 +105,9 @@ class Hook {
   /**
    * The class the hook implementation is in.
    *
-   * @var string
+   * @var class-string|null
    */
-  public string $class = '';
+  public ?string $class = NULL;
 
   /**
    * Constructs a Hook attribute object.
@@ -130,7 +130,7 @@ public function __construct(
     public string $hook,
     public string $method = '',
     public ?string $module = NULL,
-    public Order|ComplexOrder|NULL $order = NULL,
+    public Order|ComplexOrder|null $order = NULL,
   ) {}
 
   /**
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index f7bca9dbbd33..a0371d502fb4 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -73,7 +73,7 @@ public function process(ContainerBuilder $container): array {
     // List of modules implementing hooks with the implementation details.
     $implementations = [];
     // List of modules implementing hooks, used for
-    // hook_module_implementations_alter.
+    // hook_module_implements_alter().
     $legacyImplementations = [];
     // Groups of hooks that should be ordered together.
     $orderGroups = [];
@@ -150,7 +150,7 @@ public function process(ContainerBuilder $container): array {
   /**
    * Gather ordering information.
    *
-   * @param \Drupal\Core\Hook\Attributes\Hook $hook
+   * @param \Drupal\Core\Hook\Attribute\Hook $hook
    *   The hook with ordering information.
    * @param array $orderAttributes
    *   All attributes related to ordering.
@@ -159,8 +159,8 @@ public function process(ContainerBuilder $container): array {
    */
   protected function gatherOrderInformation(Hook $hook, array &$orderAttributes, array &$orderGroups): void {
     $orderAttributes[] = $hook;
-    if ($hook->order instanceof ComplexOrder && ($group = $hook->order->group)) {
-      $group[] = $hook->hook;
+    if ($hook->order instanceof ComplexOrder && $hook->order->group) {
+      $group = [...$hook->order->group, $hook->hook];
       foreach ($group as $extraHook) {
         $orderGroups[$extraHook] = array_merge($orderGroups[$extraHook] ?? [], $group);
       }
@@ -176,9 +176,9 @@ protected function gatherOrderInformation(Hook $hook, array &$orderAttributes, a
    *   The container.
    * @param \Drupal\Core\Hook\HookCollectorPass $collector
    *   The collector.
-   * @param array $implementations
-   *   All implementations.
-   * @param array $legacyImplementations
+   * @param array<string, array<string, array<class-string, list<string>>>> $implementations
+   *   All implementations, as method names keyed by hook, module and class.
+   * @param array<string, array<string, ''>> $legacyImplementations
    *   Modules that implement legacy hooks.
    * @param array $orderGroups
    *   Groups of hooks to reorder.
-- 
GitLab


From 025c800310dbc8d24b953d89e1b96af2de7b49df Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Sun, 23 Feb 2025 23:59:36 -0500
Subject: [PATCH 112/173] spacing

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 75 ++++++++++---------
 1 file changed, 40 insertions(+), 35 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index a0371d502fb4..e46e6200f423 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -70,18 +70,23 @@ class HookCollectorPass implements CompilerPassInterface {
    */
   public function process(ContainerBuilder $container): array {
     $collector = static::collectAllHookImplementations($container->getParameter('container.modules'), $container);
+
     // List of modules implementing hooks with the implementation details.
     $implementations = [];
-    // List of modules implementing hooks, used for
-    // hook_module_implements_alter().
-    $legacyImplementations = [];
+
+    // List of hooks and modules formatted for hook_module_implements_alter().
+    $legacyImplementationMap = [];
+
     // Groups of hooks that should be ordered together.
     $orderGroups = [];
-    // Attributes related to ordering hooks.
-    $orderAttributes = [];
+
+    // Hook attributes that contain ordering information.
+    $hookAttributesWithOrder = [];
+
     // List of modules that the hooks are defined for, keyed by class and
     // method.
     $moduleFinder = [];
+
     // These attributes need to be processed after all hooks have been
     // processed.
     $process_after = [
@@ -104,13 +109,13 @@ public function process(ContainerBuilder $container): array {
             $hook->set(... compact('class', 'method', 'module'));
             // Store a list of modules implementing hooks for simplifying
             // registration and hook_module_implements_alter execution.
-            $legacyImplementations[$hook->hook][$hook->module] = '';
+            $legacyImplementationMap[$hook->hook][$hook->module] = '';
             // Store the implementation details for registering the hook.
             $implementations[$hook->hook][$hook->module][$class][$hook->method] = $hook->method;
             // Reverse lookup for modules implementing hooks.
             $moduleFinder[$class][$hook->method] = $hook->module;
             if ($hook->order) {
-              $this->gatherOrderInformation($hook, $orderAttributes, $orderGroups);
+              $this->gatherOrderInformation($hook, $hookAttributesWithOrder, $orderGroups);
             }
           }
         }
@@ -123,7 +128,7 @@ public function process(ContainerBuilder $container): array {
     // discovered.
     foreach ($process_after[RemoveHook::class] as $hook) {
       if ($module = ($moduleFinder[$hook->class][$hook->method] ?? '')) {
-        unset($legacyImplementations[$hook->hook][$module]);
+        unset($legacyImplementationMap[$hook->hook][$module]);
         unset($implementations[$hook->hook][$module][$hook->class][$hook->method]);
       }
     }
@@ -133,7 +138,7 @@ public function process(ContainerBuilder $container): array {
     // but before registration to ensure this ordering directive takes
     // precedence.
     foreach ($process_after[ReOrderHook::class] as $hook) {
-      $this->gatherOrderInformation($hook, $orderAttributes, $orderGroups);
+      $this->gatherOrderInformation($hook, $hookAttributesWithOrder, $orderGroups);
     }
     $orderGroups = array_map('array_unique', $orderGroups);
 
@@ -141,8 +146,8 @@ public function process(ContainerBuilder $container): array {
     // is removed.
     // @see https://www.drupal.org/project/drupal/issues/3481778
     if (count($container->getDefinitions()) > 1) {
-      static::registerImplementations($container, $collector, $implementations, $legacyImplementations, $orderGroups);
-      static::reOrderImplementations($container, $orderAttributes, $orderGroups, $implementations, $moduleFinder);
+      static::registerImplementations($container, $collector, $implementations, $legacyImplementationMap, $orderGroups);
+      static::reOrderImplementations($container, $hookAttributesWithOrder, $orderGroups, $implementations, $moduleFinder);
     }
     return $implementations;
   }
@@ -152,13 +157,13 @@ public function process(ContainerBuilder $container): array {
    *
    * @param \Drupal\Core\Hook\Attribute\Hook $hook
    *   The hook with ordering information.
-   * @param array $orderAttributes
-   *   All attributes related to ordering.
+   * @param array $hookAttributesWithOrder
+   *   All attributes with ordering information.
    * @param array $orderGroups
    *   Groups to order by.
    */
-  protected function gatherOrderInformation(Hook $hook, array &$orderAttributes, array &$orderGroups): void {
-    $orderAttributes[] = $hook;
+  protected function gatherOrderInformation(Hook $hook, array &$hookAttributesWithOrder, array &$orderGroups): void {
+    $hookAttributesWithOrder[] = $hook;
     if ($hook->order instanceof ComplexOrder && $hook->order->group) {
       $group = [...$hook->order->group, $hook->hook];
       foreach ($group as $extraHook) {
@@ -178,12 +183,12 @@ protected function gatherOrderInformation(Hook $hook, array &$orderAttributes, a
    *   The collector.
    * @param array<string, array<string, array<class-string, list<string>>>> $implementations
    *   All implementations, as method names keyed by hook, module and class.
-   * @param array<string, array<string, ''>> $legacyImplementations
-   *   Modules that implement legacy hooks.
+   * @param array<string, array<string, ''>> $legacyImplementationMap
+   *   List of hooks and modules formatted for hook_module_implements_alter().
    * @param array $orderGroups
    *   Groups of hooks to reorder.
    */
-  protected static function registerImplementations(ContainerBuilder $container, HookCollectorPass $collector, array $implementations, array $legacyImplementations, array $orderGroups): void {
+  protected static function registerImplementations(ContainerBuilder $container, HookCollectorPass $collector, array $implementations, array $legacyImplementationMap, array $orderGroups): void {
     $container->register(ProceduralCall::class, ProceduralCall::class)
       ->addArgument($collector->includes);
     $groupIncludes = [];
@@ -195,15 +200,15 @@ protected static function registerImplementations(ContainerBuilder $container, H
       }
     }
 
-    foreach ($legacyImplementations as $hook => $moduleImplements) {
+    foreach ($legacyImplementationMap as $hook => $moduleImplements) {
       $extraHooks = $orderGroups[$hook] ?? [];
       foreach ($extraHooks as $extraHook) {
-        $moduleImplements += $legacyImplementations[$extraHook] ?? [];
+        $moduleImplements += $legacyImplementationMap[$extraHook] ?? [];
       }
       foreach ($collector->moduleImplementsAlters as $alter) {
         $alter($moduleImplements, $hook);
       }
-      $legacyImplementations[$hook] = $moduleImplements;
+      $legacyImplementationMap[$hook] = $moduleImplements;
       $priority = 0;
       foreach ($moduleImplements as $module => $v) {
         foreach ($implementations[$hook][$module] ?? [] as $class => $method_hooks) {
@@ -235,8 +240,8 @@ protected static function registerImplementations(ContainerBuilder $container, H
    *
    * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
    *   The container.
-   * @param array $orderAttributes
-   *   All attributes related to ordering.
+   * @param array $hookAttributesWithOrder
+   *   All attributes that contain ordering information.
    * @param array $orderGroups
    *   Groups to order by.
    * @param array $implementations
@@ -246,23 +251,23 @@ protected static function registerImplementations(ContainerBuilder $container, H
    *   is the module. This is not necessarily the same as the module the class
    *   is in because the implementation might be on behalf of another module.
    */
-  protected static function reOrderImplementations(ContainerBuilder $container, array $orderAttributes, array $orderGroups, array $implementations, array $moduleFinder): void {
+  protected static function reOrderImplementations(ContainerBuilder $container, array $hookAttributesWithOrder, array $orderGroups, array $implementations, array $moduleFinder): void {
     $hookPriority = new HookPriority($container);
-    foreach ($orderAttributes as $orderAttribute) {
-      assert($orderAttribute instanceof Hook);
+    foreach ($hookAttributesWithOrder as $hookAttributeWithOrder) {
+      assert($hookAttributeWithOrder instanceof Hook);
       // ::process() adds the hook serving as key to the order group so it
       // does not need to be added if there's a group for the hook.
-      $hooks = $orderGroups[$orderAttribute->hook] ?? [$orderAttribute->hook];
+      $hooks = $orderGroups[$hookAttributeWithOrder->hook] ?? [$hookAttributeWithOrder->hook];
       $combinedHook = implode(':', $hooks);
-      if ($orderAttribute->order instanceof ComplexOrder) {
+      if ($hookAttributeWithOrder->order instanceof ComplexOrder) {
         // Verify the correct structure of
-        // $orderAttribute->order->classesAndMethods and create specifiers
+        // $hookAttributeWithOrder->order->classesAndMethods and create specifiers
         // for HookPriority::change() while at it.
-        $otherSpecifiers = array_map(fn ($pair) => is_array($pair) ? $pair[0] . '::' . $pair[1] : throw new \LogicException('classesAndMethods needs to be an array of arrays'), $orderAttribute->order->classesAndMethods);
+        $otherSpecifiers = array_map(fn ($pair) => is_array($pair) ? $pair[0] . '::' . $pair[1] : throw new \LogicException('classesAndMethods needs to be an array of arrays'), $hookAttributeWithOrder->order->classesAndMethods);
         // Collect classes and methods for
         // self::registerComplexHookImplementations().
-        $classesAndMethods = $orderAttribute->order->classesAndMethods;
-        foreach ($orderAttribute->order->modules as $modules) {
+        $classesAndMethods = $hookAttributeWithOrder->order->classesAndMethods;
+        foreach ($hookAttributeWithOrder->order->modules as $modules) {
           foreach ($hooks as $hook) {
             foreach ($implementations[$hook][$modules] ?? [] as $class => $methods) {
               foreach ($methods as $method) {
@@ -273,19 +278,19 @@ protected static function reOrderImplementations(ContainerBuilder $container, ar
           }
         }
         if (count($hooks) > 1) {
-          // The hook implementation in $orderAttribute and everything in
+          // The hook implementation in $hookAttributeWithOrder and everything in
           // $classesAndMethods will be ordered relative to each other as if
           // they were implementing a single hook. This needs to be marked on
           // their service definition and added to the
           // hook_implementations_map container parameter.
-          $classesAndMethods[] = [$orderAttribute->class, $orderAttribute->method];
+          $classesAndMethods[] = [$hookAttributeWithOrder->class, $hookAttributeWithOrder->method];
           self::registerComplexHookImplementations($container, $classesAndMethods, $moduleFinder, $combinedHook);
         }
       }
       else {
         $otherSpecifiers = NULL;
       }
-      $hookPriority->change("drupal_hook.$combinedHook", $orderAttribute, $otherSpecifiers);
+      $hookPriority->change("drupal_hook.$combinedHook", $hookAttributeWithOrder, $otherSpecifiers);
     }
   }
 
-- 
GitLab


From 42cc4575da583eaa4a287f9d9f39727e1975cbf7 Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Mon, 24 Feb 2025 00:11:52 -0500
Subject: [PATCH 113/173] Remove unused line

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 1 -
 1 file changed, 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index e46e6200f423..e2864fa01fc4 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -208,7 +208,6 @@ protected static function registerImplementations(ContainerBuilder $container, H
       foreach ($collector->moduleImplementsAlters as $alter) {
         $alter($moduleImplements, $hook);
       }
-      $legacyImplementationMap[$hook] = $moduleImplements;
       $priority = 0;
       foreach ($moduleImplements as $module => $v) {
         foreach ($implementations[$hook][$module] ?? [] as $class => $method_hooks) {
-- 
GitLab


From 52cd377889bfb4a844cfe78dc70ae10dcbf4bc99 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Mon, 24 Feb 2025 09:32:42 -0500
Subject: [PATCH 114/173] Class required for check and array shapes

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 13 +++++--------
 1 file changed, 5 insertions(+), 8 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index e2864fa01fc4..a728c057d25c 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -103,7 +103,7 @@ public function process(ContainerBuilder $container): array {
               continue;
             }
             if ($class !== ProceduralCall::class) {
-              self::checkForProceduralOnlyHooks($hook);
+              self::checkForProceduralOnlyHooks($hook, $class);
             }
             // Set properties on hook class that are needed for registration.
             $hook->set(... compact('class', 'method', 'module'));
@@ -185,7 +185,7 @@ protected function gatherOrderInformation(Hook $hook, array &$hookAttributesWith
    *   All implementations, as method names keyed by hook, module and class.
    * @param array<string, array<string, ''>> $legacyImplementationMap
    *   List of hooks and modules formatted for hook_module_implements_alter().
-   * @param array $orderGroups
+   * @param array<string, list<string>> $orderGroups
    *   Groups of hooks to reorder.
    */
   protected static function registerImplementations(ContainerBuilder $container, HookCollectorPass $collector, array $implementations, array $legacyImplementationMap, array $orderGroups): void {
@@ -241,7 +241,7 @@ protected static function registerImplementations(ContainerBuilder $container, H
    *   The container.
    * @param array $hookAttributesWithOrder
    *   All attributes that contain ordering information.
-   * @param array $orderGroups
+   * @param array<string, list<string>> $orderGroups
    *   Groups to order by.
    * @param array $implementations
    *   Hook implementations.
@@ -488,10 +488,10 @@ public function getImplementations($paths): array {
    *
    * @param \Drupal\Core\Hook\Attribute\Hook $hook
    *   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 $hook, string $class): void {
     $staticDenyHooks = [
       'hook_info',
       'install',
@@ -505,9 +505,6 @@ public static function checkForProceduralOnlyHooks(Hook $hook, string $class = '
     ];
 
     if (in_array($hook->hook, $staticDenyHooks) || preg_match('/^(post_update_|preprocess_|update_\d+$)/', $hook->hook)) {
-      if (!$class) {
-        $class = $hook->class;
-      }
       throw new \LogicException("The hook $hook->hook on class $class does not support attributes and must remain procedural.");
     }
   }
-- 
GitLab


From ac17eea0dcdeb1bd91da844668d22f5af3a2f631 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Mon, 24 Feb 2025 09:47:18 -0500
Subject: [PATCH 115/173] Use cached reflection

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index a728c057d25c..408b4057d6ab 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -323,7 +323,7 @@ public static function collectAllHookImplementations(array $module_filenames, ?C
       if ($container?->hasParameter("$module.hooks_converted")) {
         $skip_procedural = $container->getParameter("$module.hooks_converted");
       }
-      $collector->collectModuleHookImplementations(dirname($info['pathname']), $module, $module_preg, $skip_procedural);
+      $collector->collectModuleHookImplementations(dirname($info['pathname']), $module, $module_preg, $skip_procedural, $container);
     }
     return $collector;
   }
@@ -340,8 +340,10 @@ public static function collectAllHookImplementations(array $module_filenames, ?C
    *   matched first.
    * @param bool $skip_procedural
    *   Skip the procedural check for the current module.
+   * @param \Symfony\Component\DependencyInjection\ContainerBuilder|null $container
+   *   The container.
    */
-  protected function collectModuleHookImplementations($dir, $module, $module_preg, bool $skip_procedural): void {
+  protected function collectModuleHookImplementations($dir, $module, $module_preg, bool $skip_procedural, ?ContainerBuilder $container = NULL): void {
     $hook_file_cache = FileCacheFactory::get('hook_implementations');
     $procedural_hook_file_cache = FileCacheFactory::get('procedural_hook_implementations:' . $module_preg);
 
@@ -372,7 +374,7 @@ protected function collectModuleHookImplementations($dir, $module, $module_preg,
           $class = str_replace('/', '\\', $class);
           $attributes = [];
           if (class_exists($class)) {
-            $reflectionClass = new \ReflectionClass($class);
+            $reflectionClass = $container?->getReflectionClass($class) ?? new \ReflectionClass($class);
             $reflections = $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC);
             $reflections[] = $reflectionClass;
             $attributes = self::getAttributeInstances($attributes, $reflections);
-- 
GitLab


From 041e7d5f980f4aa6bdcf2ced6ab8ebeba6954f0d Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Mon, 24 Feb 2025 23:13:33 -0500
Subject: [PATCH 116/173] Rename groups

---
 .../Drupal/Core/Extension/ModuleHandler.php   | 26 +++++-----
 core/lib/Drupal/Core/Hook/ComplexOrder.php    |  8 +--
 .../Drupal/Core/Hook/HookCollectorPass.php    | 50 +++++++++----------
 .../ckeditor5/src/Hook/Ckeditor5Hooks.php     |  2 +-
 ...rGroup.php => TestHookOrderExtraTypes.php} | 14 +++---
 ...rGroup.php => TestHookOrderExtraTypes.php} |  6 +--
 .../user_hooks_test/user_hooks_test.info.yml  |  0
 .../Core/Hook/HookCollectorPassTest.php       | 14 +++---
 8 files changed, 60 insertions(+), 60 deletions(-)
 rename core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/{TestHookOrderGroup.php => TestHookOrderExtraTypes.php} (65%)
 rename core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/{TestHookOrderGroup.php => TestHookOrderExtraTypes.php} (83%)
 mode change 100644 => 100755 core/modules/user/tests/modules/user_hooks_test/user_hooks_test.info.yml

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 890deaf10451..b120c85d4fc8 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -80,16 +80,16 @@ class ModuleHandler implements ModuleHandlerInterface {
    *   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 $orderGroups
-   *   A multidimensional array of hooks that have been ordered and the group
-   *   of hooks they have been ordered against. This is stored separately from
-   *   $hookImplementationsMap to prevent ordering again since this group has
-   *   already been fully ordered in HookCollectorPass.
+   * @param array $orderedExtraTypes
+   *   A multidimensional array of hooks that have been ordered and the
+   *   extra_types they have been ordered against. This is stored separately
+   *   from $hookImplementationsMap to prevent ordering again since this set
+   *   has already been fully ordered in HookCollectorPass.
    *
    * @see \Drupal\Core\DrupalKernel
    * @see \Drupal\Core\CoreServiceProvider
    */
-  public function __construct($root, array $module_list, protected EventDispatcherInterface $eventDispatcher, protected array $hookImplementationsMap, protected array $groupIncludes = [], protected array $orderGroups = []) {
+  public function __construct($root, array $module_list, protected EventDispatcherInterface $eventDispatcher, protected array $hookImplementationsMap, protected array $groupIncludes = [], protected array $orderedExtraTypes = []) {
     $this->root = $root;
     $this->moduleList = [];
     foreach ($module_list as $name => $module) {
@@ -441,16 +441,16 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
         foreach ($extra_hooks as $extra_hook) {
           $hook_listeners = $this->findListenersForAlter($extra_hook, $hook_listeners, $extra_modules);
         }
-        // Second, gather implementations grouped together. These are only used
-        // for ordering because the group might contain hooks not included in
+        // Second, gather implementations ordered together. These are only used
+        // for ordering because the set might contain hooks not included in
         // this alter() call. \Drupal\Core\Hook\HookPriority::change()
-        // registers the implementations of a grouped hook.
+        // registers the implementations of combined hooks.
         foreach (array_merge($extra_hooks, [$type . '_alter']) as $extra_hook) {
-          if (isset($this->orderGroups[$extra_hook])) {
-            $group = $this->orderGroups[$extra_hook];
-            $extra_listeners = $this->findListenersForAlter(implode(':', $group));
+          if (isset($this->orderedExtraTypes[$extra_hook])) {
+            $orderedHooks = $this->orderedExtraTypes[$extra_hook];
+            $extra_listeners = $this->findListenersForAlter(implode(':', $orderedHooks));
             // Remove already ordered hooks.
-            $extra_hooks = array_diff($extra_hooks, $group);
+            $extra_hooks = array_diff($extra_hooks, $orderedHooks);
           }
         }
       }
diff --git a/core/lib/Drupal/Core/Hook/ComplexOrder.php b/core/lib/Drupal/Core/Hook/ComplexOrder.php
index 2d9ae3839306..766256dca14b 100644
--- a/core/lib/Drupal/Core/Hook/ComplexOrder.php
+++ b/core/lib/Drupal/Core/Hook/ComplexOrder.php
@@ -37,14 +37,14 @@
    *       [Bar::class, 'someOtherMethod'],
    *     ]
    *   @endcode
-   * @param array $group
+   * @param array $extraTypes
    *   A list of hooks to be ordered together. Ordering by attributes happens
    *   at build time by setting up the order of the listeners of a hook
    *   correctly. However, ModuleHandlerInterface::alter() can be called with
    *   multiple hooks runtime. If the hook defined on this method/class
    *   requires ordering relative to other such hooks then this parameter can
-   *   be used to order relative to implementations of all hooks in the group.
-   *   Include all alter hooks to be ordered against in the group even if no
+   *   be used to order relative to implementations of all hooks in the set.
+   *   Include all alter hooks to be ordered against in the set even if no
    *   single alter() call includes all of them. For example, this can be used
    *   to order a hook_form_BASE_FORM_ID_alter() implementation relative to
    *   multiple hook_form_FORM_ID_alter() implementations as
@@ -53,7 +53,7 @@
   public function __construct(
     public array $modules = [],
     public array $classesAndMethods = [],
-    public array $group = [],
+    public array $extraTypes = [],
   ) {
     if (!$this->modules && !$this->classesAndMethods) {
       throw new \LogicException('Order must provide either modules or class-method pairs to order against.');
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 408b4057d6ab..6df5c9e6d4e2 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -77,8 +77,8 @@ public function process(ContainerBuilder $container): array {
     // List of hooks and modules formatted for hook_module_implements_alter().
     $legacyImplementationMap = [];
 
-    // Groups of hooks that should be ordered together.
-    $orderGroups = [];
+    // Hooks that should be ordered together when extra types are involved.
+    $orderExtraTypes = [];
 
     // Hook attributes that contain ordering information.
     $hookAttributesWithOrder = [];
@@ -115,7 +115,7 @@ public function process(ContainerBuilder $container): array {
             // Reverse lookup for modules implementing hooks.
             $moduleFinder[$class][$hook->method] = $hook->module;
             if ($hook->order) {
-              $this->gatherOrderInformation($hook, $hookAttributesWithOrder, $orderGroups);
+              $this->gatherOrderInformation($hook, $hookAttributesWithOrder, $orderExtraTypes);
             }
           }
         }
@@ -138,16 +138,16 @@ public function process(ContainerBuilder $container): array {
     // but before registration to ensure this ordering directive takes
     // precedence.
     foreach ($process_after[ReOrderHook::class] as $hook) {
-      $this->gatherOrderInformation($hook, $hookAttributesWithOrder, $orderGroups);
+      $this->gatherOrderInformation($hook, $hookAttributesWithOrder, $orderExtraTypes);
     }
-    $orderGroups = array_map('array_unique', $orderGroups);
+    $orderExtraTypes = array_map('array_unique', $orderExtraTypes);
 
     // @todo investigate whether this if() is needed after ModuleHandler::add()
     // is removed.
     // @see https://www.drupal.org/project/drupal/issues/3481778
     if (count($container->getDefinitions()) > 1) {
-      static::registerImplementations($container, $collector, $implementations, $legacyImplementationMap, $orderGroups);
-      static::reOrderImplementations($container, $hookAttributesWithOrder, $orderGroups, $implementations, $moduleFinder);
+      static::registerImplementations($container, $collector, $implementations, $legacyImplementationMap, $orderExtraTypes);
+      static::reOrderImplementations($container, $hookAttributesWithOrder, $orderExtraTypes, $implementations, $moduleFinder);
     }
     return $implementations;
   }
@@ -159,15 +159,15 @@ public function process(ContainerBuilder $container): array {
    *   The hook with ordering information.
    * @param array $hookAttributesWithOrder
    *   All attributes with ordering information.
-   * @param array $orderGroups
-   *   Groups to order by.
+   * @param array<string, list<string>> $orderExtraTypes
+   *   Extra types to order together with.
    */
-  protected function gatherOrderInformation(Hook $hook, array &$hookAttributesWithOrder, array &$orderGroups): void {
+  protected function gatherOrderInformation(Hook $hook, array &$hookAttributesWithOrder, array &$orderExtraTypes): void {
     $hookAttributesWithOrder[] = $hook;
-    if ($hook->order instanceof ComplexOrder && $hook->order->group) {
-      $group = [...$hook->order->group, $hook->hook];
-      foreach ($group as $extraHook) {
-        $orderGroups[$extraHook] = array_merge($orderGroups[$extraHook] ?? [], $group);
+    if ($hook->order instanceof ComplexOrder && $hook->order->extraTypes) {
+      $extraTypes = [...$hook->order->extraTypes, $hook->hook];
+      foreach ($extraTypes as $extraHook) {
+        $orderExtraTypes[$extraHook] = array_merge($orderExtraTypes[$extraHook] ?? [], $extraTypes);
       }
     }
   }
@@ -185,10 +185,10 @@ protected function gatherOrderInformation(Hook $hook, array &$hookAttributesWith
    *   All implementations, as method names keyed by hook, module and class.
    * @param array<string, array<string, ''>> $legacyImplementationMap
    *   List of hooks and modules formatted for hook_module_implements_alter().
-   * @param array<string, list<string>> $orderGroups
-   *   Groups of hooks to reorder.
+   * @param array<string, list<string>> $orderExtraTypes
+   *   Extra types to order a hook with.
    */
-  protected static function registerImplementations(ContainerBuilder $container, HookCollectorPass $collector, array $implementations, array $legacyImplementationMap, array $orderGroups): void {
+  protected static function registerImplementations(ContainerBuilder $container, HookCollectorPass $collector, array $implementations, array $legacyImplementationMap, array $orderExtraTypes): void {
     $container->register(ProceduralCall::class, ProceduralCall::class)
       ->addArgument($collector->includes);
     $groupIncludes = [];
@@ -201,7 +201,7 @@ protected static function registerImplementations(ContainerBuilder $container, H
     }
 
     foreach ($legacyImplementationMap as $hook => $moduleImplements) {
-      $extraHooks = $orderGroups[$hook] ?? [];
+      $extraHooks = $orderExtraTypes[$hook] ?? [];
       foreach ($extraHooks as $extraHook) {
         $moduleImplements += $legacyImplementationMap[$extraHook] ?? [];
       }
@@ -230,7 +230,7 @@ protected static function registerImplementations(ContainerBuilder $container, H
 
     $definition = $container->getDefinition('module_handler');
     $definition->setArgument('$groupIncludes', $groupIncludes);
-    $definition->setArgument('$orderGroups', $orderGroups);
+    $definition->setArgument('$orderedExtraTypes', $orderExtraTypes);
     $container->setParameter('hook_implementations_map', $map ?? []);
   }
 
@@ -241,8 +241,8 @@ protected static function registerImplementations(ContainerBuilder $container, H
    *   The container.
    * @param array $hookAttributesWithOrder
    *   All attributes that contain ordering information.
-   * @param array<string, list<string>> $orderGroups
-   *   Groups to order by.
+   * @param array<string, list<string>> $orderExtraTypes
+   *   Extra types to order together with.
    * @param array $implementations
    *   Hook implementations.
    * @param array $moduleFinder
@@ -250,13 +250,13 @@ protected static function registerImplementations(ContainerBuilder $container, H
    *   is the module. This is not necessarily the same as the module the class
    *   is in because the implementation might be on behalf of another module.
    */
-  protected static function reOrderImplementations(ContainerBuilder $container, array $hookAttributesWithOrder, array $orderGroups, array $implementations, array $moduleFinder): void {
+  protected static function reOrderImplementations(ContainerBuilder $container, array $hookAttributesWithOrder, array $orderExtraTypes, array $implementations, array $moduleFinder): void {
     $hookPriority = new HookPriority($container);
     foreach ($hookAttributesWithOrder as $hookAttributeWithOrder) {
       assert($hookAttributeWithOrder instanceof Hook);
-      // ::process() adds the hook serving as key to the order group so it
-      // does not need to be added if there's a group for the hook.
-      $hooks = $orderGroups[$hookAttributeWithOrder->hook] ?? [$hookAttributeWithOrder->hook];
+      // ::process() adds the hook serving as key to the order extraTypes so it
+      // does not need to be added if there's a extraTypes for the hook.
+      $hooks = $orderExtraTypes[$hookAttributeWithOrder->hook] ?? [$hookAttributeWithOrder->hook];
       $combinedHook = implode(':', $hooks);
       if ($hookAttributeWithOrder->order instanceof ComplexOrder) {
         // Verify the correct structure of
diff --git a/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php b/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php
index 1c6128a8c3cd..0f0b937f30b5 100644
--- a/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php
+++ b/core/modules/ckeditor5/src/Hook/Ckeditor5Hooks.php
@@ -112,7 +112,7 @@ public function theme() : array {
   #[Hook('form_filter_format_form_alter',
     order: new OrderAfter(
       modules: ['editor', 'media'],
-      group: ['form_filter_format_add_form_alter', 'form_filter_format_edit_form_alter'],
+      extraTypes: ['form_filter_format_add_form_alter', 'form_filter_format_edit_form_alter'],
     )
   )]
   public function formFilterFormatFormAlter(array &$form, FormStateInterface $form_state, $form_id) : void {
diff --git a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderGroup.php b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderExtraTypes.php
similarity index 65%
rename from core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderGroup.php
rename to core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderExtraTypes.php
index 87b441159a3d..321cc19e5bf1 100644
--- a/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderGroup.php
+++ b/core/modules/system/tests/modules/hook_order_first_alphabetically/src/Hook/TestHookOrderExtraTypes.php
@@ -19,23 +19,23 @@
  *
  * Each method pair tests one hook ordering permutation.
  */
-class TestHookOrderGroup {
+class TestHookOrderExtraTypes {
 
   /**
-   * This pair tests OrderAfter with Group.
+   * This pair tests OrderAfter with ExtraTypes.
    */
   #[Hook('custom_hook_extra_types1_alter',
     order: new OrderAfter(
       modules: ['hook_order_last_alphabetically'],
-      group: ['custom_hook_extra_types2_alter'],
+      extraTypes: ['custom_hook_extra_types2_alter'],
     )
   )]
   public static function customHookExtraTypes(): void {
-    // This should be run after so HookOrderGroupExtraTypes should not be set.
-    if (!isset($GLOBALS['HookOrderGroupExtraTypes'])) {
-      $GLOBALS['HookOutOfOrderTestingOrderGroupsExtraTypes'] = 'HookOutOfOrderTestingOrderGroupsExtraTypes';
+    // This should be run after so HookOrderExtraTypes should not be set.
+    if (!isset($GLOBALS['HookOrderExtraTypes'])) {
+      $GLOBALS['HookOutOfOrderTestingOrderExtraTypes'] = 'HookOutOfOrderTestingOrderExtraTypes';
     }
-    $GLOBALS['HookRanTestingOrderGroupsExtraTypes'] = 'HookRanTestingOrderGroupsExtraTypes';
+    $GLOBALS['HookRanTestingOrderExtraTypes'] = 'HookRanTestingOrderExtraTypes';
   }
 
 }
diff --git a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderGroup.php b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderExtraTypes.php
similarity index 83%
rename from core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderGroup.php
rename to core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderExtraTypes.php
index faac9868ce90..2ca8a0d93f42 100644
--- a/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderGroup.php
+++ b/core/modules/system/tests/modules/hook_order_last_alphabetically/src/Hook/TestHookOrderExtraTypes.php
@@ -18,14 +18,14 @@
  *
  * Each method pair tests one hook ordering permutation.
  */
-class TestHookOrderGroup {
+class TestHookOrderExtraTypes {
 
   /**
-   * This pair tests OrderAfter with Group.
+   * This pair tests OrderAfter with ExtraTypes.
    */
   #[Hook('custom_hook_extra_types2_alter')]
   public static function customHookExtraTypes(): void {
-    $GLOBALS['HookOrderGroupExtraTypes'] = 'HookOrderGroupExtraTypes';
+    $GLOBALS['HookOrderExtraTypes'] = 'HookOrderExtraTypes';
   }
 
 }
diff --git a/core/modules/user/tests/modules/user_hooks_test/user_hooks_test.info.yml b/core/modules/user/tests/modules/user_hooks_test/user_hooks_test.info.yml
old mode 100644
new mode 100755
diff --git a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
index 38740b09d818..9c3e6978d3d8 100644
--- a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
@@ -211,13 +211,13 @@ public function testHookBefore(): void {
   /**
    * Tests hook ordering with attributes.
    */
-  public function testHookOrderGroup(): void {
+  public function testHookOrderExtraTypes(): void {
     $module_installer = $this->container->get('module_installer');
     $this->assertTrue($module_installer->install(['hook_order_first_alphabetically']));
     $this->assertTrue($module_installer->install(['hook_order_last_alphabetically']));
-    $this->assertFalse(isset($GLOBALS['HookOrderGroupExtraTypes']));
-    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingOrderGroupsExtraTypes']));
-    $this->assertFalse(isset($GLOBALS['HookRanTestingOrderGroupsExtraTypes']));
+    $this->assertFalse(isset($GLOBALS['HookOrderExtraTypes']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingOrderExtraTypes']));
+    $this->assertFalse(isset($GLOBALS['HookRanTestingOrderExtraTypes']));
     $module_handler = $this->container->get('module_handler');
     $hooks = [
       'custom_hook',
@@ -226,9 +226,9 @@ public function testHookOrderGroup(): void {
     ];
     $data = ['hi'];
     $module_handler->alter($hooks, $data);
-    $this->assertTrue(isset($GLOBALS['HookOrderGroupExtraTypes']));
-    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingOrderGroupsExtraTypes']));
-    $this->assertTrue(isset($GLOBALS['HookRanTestingOrderGroupsExtraTypes']));
+    $this->assertTrue(isset($GLOBALS['HookOrderExtraTypes']));
+    $this->assertFalse(isset($GLOBALS['HookOutOfOrderTestingOrderExtraTypes']));
+    $this->assertTrue(isset($GLOBALS['HookRanTestingOrderExtraTypes']));
   }
 
   /**
-- 
GitLab


From 82cef566422c8e2c69bd66428198048c53a4da7c Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 25 Feb 2025 15:19:57 -0500
Subject: [PATCH 117/173] HookOperation Base

---
 core/lib/Drupal/Core/Hook/Attribute/Hook.php  | 22 +++++------
 .../Core/Hook/Attribute/ReOrderHook.php       | 16 ++++----
 .../Drupal/Core/Hook/Attribute/RemoveHook.php | 16 ++++----
 .../Drupal/Core/Hook/HookCollectorPass.php    | 24 ++++++------
 core/lib/Drupal/Core/Hook/HookOperation.php   | 39 +++++++++++++++++++
 core/lib/Drupal/Core/Hook/HookPriority.php    |  5 +--
 6 files changed, 79 insertions(+), 43 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Hook/HookOperation.php

diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
index 151f308b5508..b9b9a9caddff 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -5,6 +5,7 @@
 namespace Drupal\Core\Hook\Attribute;
 
 use Drupal\Core\Hook\ComplexOrder;
+use Drupal\Core\Hook\HookOperation;
 use Drupal\Core\Hook\Order;
 
 /**
@@ -100,14 +101,7 @@
  * @internal
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
-class Hook {
-
-  /**
-   * The class the hook implementation is in.
-   *
-   * @var class-string|null
-   */
-  public ?string $class = NULL;
+class Hook extends HookOperation {
 
   /**
    * Constructs a Hook attribute object.
@@ -127,11 +121,13 @@ class Hook {
    *   (optional) Set the order of the implementation.
    */
   public function __construct(
-    public string $hook,
-    public string $method = '',
-    public ?string $module = NULL,
-    public Order|ComplexOrder|null $order = NULL,
-  ) {}
+    string $hook,
+    ?string $method = '',
+    ?string $module = NULL,
+    Order|ComplexOrder|null $order = NULL,
+  ) {
+    parent::__construct(... compact('hook', 'method', 'module', 'order'));
+  }
 
   /**
    * Set necessary parameters for the hook attribute.
diff --git a/core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php b/core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php
index 98ce8aff5602..398039ab7e7c 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php
@@ -5,6 +5,7 @@
 namespace Drupal\Core\Hook\Attribute;
 
 use Drupal\Core\Hook\ComplexOrder;
+use Drupal\Core\Hook\HookOperation;
 use Drupal\Core\Hook\Order;
 
 /**
@@ -13,18 +14,18 @@
  * @internal
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
-class ReOrderHook extends Hook {
+class ReOrderHook extends HookOperation {
 
   /**
-   * Constructs a Hook attribute object.
+   * Constructs a ReOrderHook object.
    *
    * @param string $hook
-   *   The short hook name, without the 'hook_' prefix.
+   *   The hook parameter of the #Hook being modified.
    * @param class-string $class
-   *   The class the implementation to modify is in. This allows one module to
-   *   affect the order of another module's hook.
+   *   The class the implementation to modify is in.
    * @param string $method
-   *   The method name of the implementation to modify.
+   *   The method name of the #Hook being modified. If the hook attribute is
+   *   on a class and does not have method set, then use __invoke.
    * @param \Drupal\Core\Hook\Order|\Drupal\Core\Hook\ComplexOrder $order
    *   Set the order of the implementation.
    */
@@ -34,8 +35,7 @@ public function __construct(
     string $method,
     Order|ComplexOrder $order,
   ) {
-    parent::__construct($hook, method: $method, order: $order);
-    $this->class = $class;
+    parent::__construct(... compact('hook', 'method', 'class', 'order'));
   }
 
 }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php b/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
index 153a5c198522..0f9306a301ff 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
@@ -4,31 +4,33 @@
 
 namespace Drupal\Core\Hook\Attribute;
 
+use Drupal\Core\Hook\HookOperation;
+
 /**
  * Attribute for removing an implementation.
  *
  * @internal
  */
 #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
-class RemoveHook extends Hook {
+class RemoveHook extends HookOperation {
 
   /**
-   * Constructs a Hook attribute object.
+   * Constructs a RemoveHook object.
    *
    * @param string $hook
-   *   The short hook name, without the 'hook_' prefix.
+   *   The hook parameter of the #Hook being modified.
    * @param class-string $class
-   *   The class the implementation to remove is in.
+   *   The class the implementation to modify is in.
    * @param string $method
-   *   The method name of the implementation to remove.
+   *   The method name of the #Hook being modified. If the hook attribute is
+   *   on a class and does not have method set, then use __invoke.
    */
   public function __construct(
     string $hook,
     string $class,
     string $method,
   ) {
-    parent::__construct($hook, method: $method);
-    $this->class = $class;
+    parent::__construct(... compact('hook', 'method', 'class'));
   }
 
 }
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 6df5c9e6d4e2..566fcb7159cb 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -97,7 +97,7 @@ public function process(ContainerBuilder $container): array {
       foreach ($collector->moduleHooks[$module] ?? [] as $class => $methods) {
         foreach ($methods as $method => $hooks) {
           foreach ($hooks as $hook) {
-            assert($hook instanceof Hook);
+            assert($hook instanceof HookOperation);
             if (isset($process_after[get_class($hook)])) {
               $process_after[get_class($hook)][] = $hook;
               continue;
@@ -126,10 +126,10 @@ public function process(ContainerBuilder $container): array {
     // registering the hooks. This must happen after all collection, but before
     // registration to ensure the hook it is removing has already been
     // discovered.
-    foreach ($process_after[RemoveHook::class] as $hook) {
-      if ($module = ($moduleFinder[$hook->class][$hook->method] ?? '')) {
-        unset($legacyImplementationMap[$hook->hook][$module]);
-        unset($implementations[$hook->hook][$module][$hook->class][$hook->method]);
+    foreach ($process_after[RemoveHook::class] as $removeHook) {
+      if ($module = ($moduleFinder[$removeHook->class][$removeHook->method] ?? '')) {
+        unset($legacyImplementationMap[$removeHook->hook][$module]);
+        unset($implementations[$removeHook->hook][$module][$removeHook->class][$removeHook->method]);
       }
     }
 
@@ -137,8 +137,8 @@ public function process(ContainerBuilder $container): array {
     // before registering the hooks. This must happen after all collection,
     // but before registration to ensure this ordering directive takes
     // precedence.
-    foreach ($process_after[ReOrderHook::class] as $hook) {
-      $this->gatherOrderInformation($hook, $hookAttributesWithOrder, $orderExtraTypes);
+    foreach ($process_after[ReOrderHook::class] as $reOrderHook) {
+      $this->gatherOrderInformation($reOrderHook, $hookAttributesWithOrder, $orderExtraTypes);
     }
     $orderExtraTypes = array_map('array_unique', $orderExtraTypes);
 
@@ -155,14 +155,14 @@ public function process(ContainerBuilder $container): array {
   /**
    * Gather ordering information.
    *
-   * @param \Drupal\Core\Hook\Attribute\Hook $hook
+   * @param \Drupal\Core\Hook\HookOperation $hook
    *   The hook with ordering information.
    * @param array $hookAttributesWithOrder
    *   All attributes with ordering information.
    * @param array<string, list<string>> $orderExtraTypes
    *   Extra types to order together with.
    */
-  protected function gatherOrderInformation(Hook $hook, array &$hookAttributesWithOrder, array &$orderExtraTypes): void {
+  protected function gatherOrderInformation(HookOperation $hook, array &$hookAttributesWithOrder, array &$orderExtraTypes): void {
     $hookAttributesWithOrder[] = $hook;
     if ($hook->order instanceof ComplexOrder && $hook->order->extraTypes) {
       $extraTypes = [...$hook->order->extraTypes, $hook->hook];
@@ -253,7 +253,7 @@ protected static function registerImplementations(ContainerBuilder $container, H
   protected static function reOrderImplementations(ContainerBuilder $container, array $hookAttributesWithOrder, array $orderExtraTypes, array $implementations, array $moduleFinder): void {
     $hookPriority = new HookPriority($container);
     foreach ($hookAttributesWithOrder as $hookAttributeWithOrder) {
-      assert($hookAttributeWithOrder instanceof Hook);
+      assert($hookAttributeWithOrder instanceof HookOperation);
       // ::process() adds the hook serving as key to the order extraTypes so it
       // does not need to be added if there's a extraTypes for the hook.
       $hooks = $orderExtraTypes[$hookAttributeWithOrder->hook] ?? [$hookAttributeWithOrder->hook];
@@ -488,7 +488,7 @@ public function getImplementations($paths): array {
   /**
    * Checks for hooks which can't be supported in classes.
    *
-   * @param \Drupal\Core\Hook\Attribute\Hook $hook
+   * @param \Drupal\Core\Hook\Hook $hook
    *   The hook to check.
    * @param class-string $class
    *   The class the hook is implemented on.
@@ -524,7 +524,7 @@ public static function checkForProceduralOnlyHooks(Hook $hook, string $class): v
    */
   protected static function getAttributeInstances(array $attributes, array $reflections): array {
     foreach ($reflections as $reflection) {
-      if ($reflection_attributes = $reflection->getAttributes(Hook::class, \ReflectionAttribute::IS_INSTANCEOF)) {
+      if ($reflection_attributes = $reflection->getAttributes(HookOperation::class, \ReflectionAttribute::IS_INSTANCEOF)) {
         $method = $reflection instanceof \ReflectionMethod ? $reflection->getName() : '__invoke';
         $attributes[$method] = array_map(fn (\ReflectionAttribute $ra) => $ra->newInstance(), $reflection_attributes);
       }
diff --git a/core/lib/Drupal/Core/Hook/HookOperation.php b/core/lib/Drupal/Core/Hook/HookOperation.php
new file mode 100644
index 000000000000..42cfa06317c6
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/HookOperation.php
@@ -0,0 +1,39 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook;
+
+/**
+ * Base class for attributes that affect other hook implementations.
+ *
+ * @internal
+ */
+abstract class HookOperation {
+
+  /**
+   * Constructs a HookOperation object.
+   *
+   * @param string $hook
+   *   The hook parameter of the implementation.
+   * @param string $method
+   *   The method name of the implementation. If the hook attribute is
+   *   on a class and does not have method set, then use __invoke.
+   * @param class-string $class
+   *   (optional) The class the implementation to modify is in.
+   * @param string|null $module
+   *   (optional) The module this implementation is for. This allows one module
+   *   to implement a hook on behalf of another module. Defaults to the module
+   *   the implementation is in.
+   * @param \Drupal\Core\Hook\Order|\Drupal\Core\Hook\ComplexOrder|null $order
+   *   (optional) Set the order of the implementation.
+   */
+  public function __construct(
+    public string $hook,
+    public string $method,
+    public ?string $class = '',
+    public ?string $module = NULL,
+    public Order|ComplexOrder|null $order = NULL,
+  ) {}
+
+}
diff --git a/core/lib/Drupal/Core/Hook/HookPriority.php b/core/lib/Drupal/Core/Hook/HookPriority.php
index 1fc4df0abd55..36f6a076b6ef 100644
--- a/core/lib/Drupal/Core/Hook/HookPriority.php
+++ b/core/lib/Drupal/Core/Hook/HookPriority.php
@@ -4,7 +4,6 @@
 
 namespace Drupal\Core\Hook;
 
-use Drupal\Core\Hook\Attribute\Hook;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 
 /**
@@ -21,7 +20,7 @@ public function __construct(protected ContainerBuilder $container) {}
    *
    * @param string $event
    *   Listeners to this event will be ordered.
-   * @param \Drupal\Core\Hook\Attribute\Hook $hook
+   * @param \Drupal\Core\Hook\HookOperation $hook
    *   The hook attribute. Most of the order parameter is ignored by this
    *   class, only $hook->order->value is used. The rest is preprocessed by
    *   HookCollectorPass and passed in $other_specifiers.
@@ -31,7 +30,7 @@ public function __construct(protected ContainerBuilder $container) {}
    *
    * @internal
    */
-  public function change(string $event, Hook $hook, ?array $other_specifiers = NULL): void {
+  public function change(string $event, HookOperation $hook, ?array $other_specifiers = NULL): void {
     foreach ($this->container->findTaggedServiceIds('kernel.event_listener') as $id => $tags) {
       foreach ($tags as $key => $tag) {
         if ($tag['event'] === $event) {
-- 
GitLab


From 59239dfcb0a1557cd4766ec89a0f4e6b70be4d21 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 25 Feb 2025 15:23:34 -0500
Subject: [PATCH 118/173] Rename hookAttributesWithOrder to hookOrderOperations

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 40 +++++++++----------
 1 file changed, 20 insertions(+), 20 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 566fcb7159cb..1871591626a4 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -81,7 +81,7 @@ public function process(ContainerBuilder $container): array {
     $orderExtraTypes = [];
 
     // Hook attributes that contain ordering information.
-    $hookAttributesWithOrder = [];
+    $hookOrderOperations = [];
 
     // List of modules that the hooks are defined for, keyed by class and
     // method.
@@ -115,7 +115,7 @@ public function process(ContainerBuilder $container): array {
             // Reverse lookup for modules implementing hooks.
             $moduleFinder[$class][$hook->method] = $hook->module;
             if ($hook->order) {
-              $this->gatherOrderInformation($hook, $hookAttributesWithOrder, $orderExtraTypes);
+              $this->gatherOrderInformation($hook, $hookOrderOperations, $orderExtraTypes);
             }
           }
         }
@@ -138,7 +138,7 @@ public function process(ContainerBuilder $container): array {
     // but before registration to ensure this ordering directive takes
     // precedence.
     foreach ($process_after[ReOrderHook::class] as $reOrderHook) {
-      $this->gatherOrderInformation($reOrderHook, $hookAttributesWithOrder, $orderExtraTypes);
+      $this->gatherOrderInformation($reOrderHook, $hookOrderOperations, $orderExtraTypes);
     }
     $orderExtraTypes = array_map('array_unique', $orderExtraTypes);
 
@@ -147,7 +147,7 @@ public function process(ContainerBuilder $container): array {
     // @see https://www.drupal.org/project/drupal/issues/3481778
     if (count($container->getDefinitions()) > 1) {
       static::registerImplementations($container, $collector, $implementations, $legacyImplementationMap, $orderExtraTypes);
-      static::reOrderImplementations($container, $hookAttributesWithOrder, $orderExtraTypes, $implementations, $moduleFinder);
+      static::reOrderImplementations($container, $hookOrderOperations, $orderExtraTypes, $implementations, $moduleFinder);
     }
     return $implementations;
   }
@@ -157,13 +157,13 @@ public function process(ContainerBuilder $container): array {
    *
    * @param \Drupal\Core\Hook\HookOperation $hook
    *   The hook with ordering information.
-   * @param array $hookAttributesWithOrder
+   * @param array $hookOrderOperations
    *   All attributes with ordering information.
    * @param array<string, list<string>> $orderExtraTypes
    *   Extra types to order together with.
    */
-  protected function gatherOrderInformation(HookOperation $hook, array &$hookAttributesWithOrder, array &$orderExtraTypes): void {
-    $hookAttributesWithOrder[] = $hook;
+  protected function gatherOrderInformation(HookOperation $hook, array &$hookOrderOperations, array &$orderExtraTypes): void {
+    $hookOrderOperations[] = $hook;
     if ($hook->order instanceof ComplexOrder && $hook->order->extraTypes) {
       $extraTypes = [...$hook->order->extraTypes, $hook->hook];
       foreach ($extraTypes as $extraHook) {
@@ -239,7 +239,7 @@ protected static function registerImplementations(ContainerBuilder $container, H
    *
    * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
    *   The container.
-   * @param array $hookAttributesWithOrder
+   * @param array $hookOrderOperations
    *   All attributes that contain ordering information.
    * @param array<string, list<string>> $orderExtraTypes
    *   Extra types to order together with.
@@ -250,23 +250,23 @@ protected static function registerImplementations(ContainerBuilder $container, H
    *   is the module. This is not necessarily the same as the module the class
    *   is in because the implementation might be on behalf of another module.
    */
-  protected static function reOrderImplementations(ContainerBuilder $container, array $hookAttributesWithOrder, array $orderExtraTypes, array $implementations, array $moduleFinder): void {
+  protected static function reOrderImplementations(ContainerBuilder $container, array $hookOrderOperations, array $orderExtraTypes, array $implementations, array $moduleFinder): void {
     $hookPriority = new HookPriority($container);
-    foreach ($hookAttributesWithOrder as $hookAttributeWithOrder) {
-      assert($hookAttributeWithOrder instanceof HookOperation);
+    foreach ($hookOrderOperations as $hookOrderOperation) {
+      assert($hookOrderOperation instanceof HookOperation);
       // ::process() adds the hook serving as key to the order extraTypes so it
       // does not need to be added if there's a extraTypes for the hook.
-      $hooks = $orderExtraTypes[$hookAttributeWithOrder->hook] ?? [$hookAttributeWithOrder->hook];
+      $hooks = $orderExtraTypes[$hookOrderOperation->hook] ?? [$hookOrderOperation->hook];
       $combinedHook = implode(':', $hooks);
-      if ($hookAttributeWithOrder->order instanceof ComplexOrder) {
+      if ($hookOrderOperation->order instanceof ComplexOrder) {
         // Verify the correct structure of
-        // $hookAttributeWithOrder->order->classesAndMethods and create specifiers
+        // $hookOrderOperation->order->classesAndMethods and create specifiers
         // for HookPriority::change() while at it.
-        $otherSpecifiers = array_map(fn ($pair) => is_array($pair) ? $pair[0] . '::' . $pair[1] : throw new \LogicException('classesAndMethods needs to be an array of arrays'), $hookAttributeWithOrder->order->classesAndMethods);
+        $otherSpecifiers = array_map(fn ($pair) => is_array($pair) ? $pair[0] . '::' . $pair[1] : throw new \LogicException('classesAndMethods needs to be an array of arrays'), $hookOrderOperation->order->classesAndMethods);
         // Collect classes and methods for
         // self::registerComplexHookImplementations().
-        $classesAndMethods = $hookAttributeWithOrder->order->classesAndMethods;
-        foreach ($hookAttributeWithOrder->order->modules as $modules) {
+        $classesAndMethods = $hookOrderOperation->order->classesAndMethods;
+        foreach ($hookOrderOperation->order->modules as $modules) {
           foreach ($hooks as $hook) {
             foreach ($implementations[$hook][$modules] ?? [] as $class => $methods) {
               foreach ($methods as $method) {
@@ -277,19 +277,19 @@ protected static function reOrderImplementations(ContainerBuilder $container, ar
           }
         }
         if (count($hooks) > 1) {
-          // The hook implementation in $hookAttributeWithOrder and everything in
+          // The hook implementation in $hookOrderOperation and everything in
           // $classesAndMethods will be ordered relative to each other as if
           // they were implementing a single hook. This needs to be marked on
           // their service definition and added to the
           // hook_implementations_map container parameter.
-          $classesAndMethods[] = [$hookAttributeWithOrder->class, $hookAttributeWithOrder->method];
+          $classesAndMethods[] = [$hookOrderOperation->class, $hookOrderOperation->method];
           self::registerComplexHookImplementations($container, $classesAndMethods, $moduleFinder, $combinedHook);
         }
       }
       else {
         $otherSpecifiers = NULL;
       }
-      $hookPriority->change("drupal_hook.$combinedHook", $hookAttributeWithOrder, $otherSpecifiers);
+      $hookPriority->change("drupal_hook.$combinedHook", $hookOrderOperation, $otherSpecifiers);
     }
   }
 
-- 
GitLab


From b5c0d87f180e136a293be76e4da07e1a478397c7 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Tue, 25 Feb 2025 16:53:14 -0500
Subject: [PATCH 119/173] Move module to Hook

---
 core/lib/Drupal/Core/Hook/Attribute/Hook.php    | 6 +++---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 2 +-
 core/lib/Drupal/Core/Hook/HookOperation.php     | 5 -----
 3 files changed, 4 insertions(+), 9 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
index b9b9a9caddff..4b77b1e5e301 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -108,7 +108,7 @@ class Hook extends HookOperation {
    *
    * @param string $hook
    *   The short hook name, without the 'hook_' prefix.
-   * @param string $method
+   * @param ?string $method
    *   (optional) The method name. If this attribute is on a method, this
    *   parameter is not required. If this attribute is on a class and this
    *   parameter is omitted, the class must have an __invoke() method, which is
@@ -123,10 +123,10 @@ class Hook extends HookOperation {
   public function __construct(
     string $hook,
     ?string $method = '',
-    ?string $module = NULL,
+    public ?string $module = NULL,
     Order|ComplexOrder|null $order = NULL,
   ) {
-    parent::__construct(... compact('hook', 'method', 'module', 'order'));
+    parent::__construct(... compact('hook', 'method', 'order'));
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 1871591626a4..87c36c7ee263 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -165,7 +165,7 @@ public function process(ContainerBuilder $container): array {
   protected function gatherOrderInformation(HookOperation $hook, array &$hookOrderOperations, array &$orderExtraTypes): void {
     $hookOrderOperations[] = $hook;
     if ($hook->order instanceof ComplexOrder && $hook->order->extraTypes) {
-      $extraTypes = [...$hook->order->extraTypes, $hook->hook];
+      $extraTypes = [... $hook->order->extraTypes, $hook->hook];
       foreach ($extraTypes as $extraHook) {
         $orderExtraTypes[$extraHook] = array_merge($orderExtraTypes[$extraHook] ?? [], $extraTypes);
       }
diff --git a/core/lib/Drupal/Core/Hook/HookOperation.php b/core/lib/Drupal/Core/Hook/HookOperation.php
index 42cfa06317c6..ce4932767a23 100644
--- a/core/lib/Drupal/Core/Hook/HookOperation.php
+++ b/core/lib/Drupal/Core/Hook/HookOperation.php
@@ -21,10 +21,6 @@ abstract class HookOperation {
    *   on a class and does not have method set, then use __invoke.
    * @param class-string $class
    *   (optional) The class the implementation to modify is in.
-   * @param string|null $module
-   *   (optional) The module this implementation is for. This allows one module
-   *   to implement a hook on behalf of another module. Defaults to the module
-   *   the implementation is in.
    * @param \Drupal\Core\Hook\Order|\Drupal\Core\Hook\ComplexOrder|null $order
    *   (optional) Set the order of the implementation.
    */
@@ -32,7 +28,6 @@ public function __construct(
     public string $hook,
     public string $method,
     public ?string $class = '',
-    public ?string $module = NULL,
     public Order|ComplexOrder|null $order = NULL,
   ) {}
 
-- 
GitLab


From ce66cc1b6e8edf175b9c1e5bbd13a37a88eb8176 Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Tue, 25 Feb 2025 23:07:57 -0500
Subject: [PATCH 120/173] Refactor GatherOrderInformation away

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 35 +++++++------------
 core/lib/Drupal/Core/Hook/HookOperation.php   | 14 ++++----
 2 files changed, 20 insertions(+), 29 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 87c36c7ee263..7d329a1fb907 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -115,7 +115,7 @@ public function process(ContainerBuilder $container): array {
             // Reverse lookup for modules implementing hooks.
             $moduleFinder[$class][$hook->method] = $hook->module;
             if ($hook->order) {
-              $this->gatherOrderInformation($hook, $hookOrderOperations, $orderExtraTypes);
+              $hookOrderOperations[] = $hook;
             }
           }
         }
@@ -133,12 +133,21 @@ public function process(ContainerBuilder $container): array {
       }
     }
 
-    // Loop over all ReOrderHook attributes and remove them from the maps
+    // Loop over all ReOrderHook attributes and gather order information
     // before registering the hooks. This must happen after all collection,
     // but before registration to ensure this ordering directive takes
     // precedence.
     foreach ($process_after[ReOrderHook::class] as $reOrderHook) {
-      $this->gatherOrderInformation($reOrderHook, $hookOrderOperations, $orderExtraTypes);
+      $hookOrderOperations[] = $reOrderHook;
+    }
+
+    foreach ($hookOrderOperations as $hookWithOrder) {
+      if ($hookWithOrder->order instanceof ComplexOrder && $hookWithOrder->order->extraTypes) {
+        $extraTypes = [... $hookWithOrder->order->extraTypes, $hookWithOrder->hook];
+        foreach ($extraTypes as $extraHook) {
+          $orderExtraTypes[$extraHook] = array_merge($orderExtraTypes[$extraHook] ?? [], $extraTypes);
+        }
+      }
     }
     $orderExtraTypes = array_map('array_unique', $orderExtraTypes);
 
@@ -152,26 +161,6 @@ public function process(ContainerBuilder $container): array {
     return $implementations;
   }
 
-  /**
-   * Gather ordering information.
-   *
-   * @param \Drupal\Core\Hook\HookOperation $hook
-   *   The hook with ordering information.
-   * @param array $hookOrderOperations
-   *   All attributes with ordering information.
-   * @param array<string, list<string>> $orderExtraTypes
-   *   Extra types to order together with.
-   */
-  protected function gatherOrderInformation(HookOperation $hook, array &$hookOrderOperations, array &$orderExtraTypes): void {
-    $hookOrderOperations[] = $hook;
-    if ($hook->order instanceof ComplexOrder && $hook->order->extraTypes) {
-      $extraTypes = [... $hook->order->extraTypes, $hook->hook];
-      foreach ($extraTypes as $extraHook) {
-        $orderExtraTypes[$extraHook] = array_merge($orderExtraTypes[$extraHook] ?? [], $extraTypes);
-      }
-    }
-  }
-
   /**
    * Register hook implementations as event listeners.
    *
diff --git a/core/lib/Drupal/Core/Hook/HookOperation.php b/core/lib/Drupal/Core/Hook/HookOperation.php
index ce4932767a23..6794a536552a 100644
--- a/core/lib/Drupal/Core/Hook/HookOperation.php
+++ b/core/lib/Drupal/Core/Hook/HookOperation.php
@@ -5,7 +5,7 @@
 namespace Drupal\Core\Hook;
 
 /**
- * Base class for attributes that affect other hook implementations.
+ * Base class for attributes that affect or define hook implementations.
  *
  * @internal
  */
@@ -15,14 +15,16 @@ abstract class HookOperation {
    * Constructs a HookOperation object.
    *
    * @param string $hook
-   *   The hook parameter of the implementation.
+   *   The hook being implemented or modified.
    * @param string $method
-   *   The method name of the implementation. If the hook attribute is
-   *   on a class and does not have method set, then use __invoke.
+   *   The method for the hook being implemented or modified.
+   *   This is required when modifying existing hook implementations it is
+   *   optional otherwise. See \Drupal\Core\Hook\Attribute\Hook for more
+   *   information.
    * @param class-string $class
-   *   (optional) The class the implementation to modify is in.
+   *   (optional) The class of the hook being implemented or modified.
    * @param \Drupal\Core\Hook\Order|\Drupal\Core\Hook\ComplexOrder|null $order
-   *   (optional) Set the order of the implementation.
+   *   (optional) Set the order of the hook referenced.
    */
   public function __construct(
     public string $hook,
-- 
GitLab


From 1428914e4c7f354528deb7507659e1c5d6be9f4a Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Tue, 25 Feb 2025 23:33:38 -0500
Subject: [PATCH 121/173] Refactor getAttributeInstances

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 14 ++++++--------
 1 file changed, 6 insertions(+), 8 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 7d329a1fb907..51a18a90ae5b 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -364,9 +364,7 @@ protected function collectModuleHookImplementations($dir, $module, $module_preg,
           $attributes = [];
           if (class_exists($class)) {
             $reflectionClass = $container?->getReflectionClass($class) ?? new \ReflectionClass($class);
-            $reflections = $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC);
-            $reflections[] = $reflectionClass;
-            $attributes = self::getAttributeInstances($attributes, $reflections);
+            $attributes = self::getAttributeInstances($reflectionClass);
             $hook_file_cache->set($filename, ['class' => $class, 'attributes' => $attributes]);
           }
         }
@@ -503,15 +501,15 @@ public static function checkForProceduralOnlyHooks(Hook $hook, string $class): v
   /**
    * Get attribute instances from class and method reflections.
    *
-   * @param array $attributes
-   *   The current attributes.
-   * @param array $reflections
-   *   A list of class and method reflections.
+   * @param \ReflectionClass $reflectionClass
+   *   A reflected class.
    *
    * @return array
    *   A list of Hook attribute instances.
    */
-  protected static function getAttributeInstances(array $attributes, array $reflections): array {
+  protected static function getAttributeInstances(\ReflectionClass $reflectionClass): array {
+    $reflections = $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC);
+    $reflections[] = $reflectionClass;
     foreach ($reflections as $reflection) {
       if ($reflection_attributes = $reflection->getAttributes(HookOperation::class, \ReflectionAttribute::IS_INSTANCEOF)) {
         $method = $reflection instanceof \ReflectionMethod ? $reflection->getName() : '__invoke';
-- 
GitLab


From bebd9f2a56b573c2259f2cb3eff81681101e81a3 Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Tue, 25 Feb 2025 23:38:26 -0500
Subject: [PATCH 122/173] Initialize attributes

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 1 +
 1 file changed, 1 insertion(+)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 51a18a90ae5b..efa58463775f 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -508,6 +508,7 @@ public static function checkForProceduralOnlyHooks(Hook $hook, string $class): v
    *   A list of Hook attribute instances.
    */
   protected static function getAttributeInstances(\ReflectionClass $reflectionClass): array {
+    $attributes = [];
     $reflections = $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC);
     $reflections[] = $reflectionClass;
     foreach ($reflections as $reflection) {
-- 
GitLab


From 2edbec3d186f0e57a3ccd4667dd6c2be0a22222a Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Wed, 26 Feb 2025 14:05:41 -0500
Subject: [PATCH 123/173] Deprecate HMIA

---
 core/core.api.php                             |  1 +
 core/lib/Drupal/Core/Extension/module.api.php | 10 +++++
 .../Attribute/LegacyModuleImplementsAlter.php | 26 +++++++++++
 .../Drupal/Core/Hook/HookCollectorPass.php    |  5 ++-
 .../modules/common_test/common_test.module    | 18 --------
 ..._implements_alter_test.implementations.inc | 17 ++++++++
 .../module_implements_alter_test.info.yml     |  6 +++
 .../module_implements_alter_test.module       | 42 ++++++++++++++++++
 .../modules/module_test/module_test.module    | 20 ---------
 .../tests/src/Kernel/Common/AlterTest.php     |  3 ++
 .../Extension/ModuleImplementsAlterTest.php   | 43 +++++++++----------
 .../Core/Hook/HookCollectorPassTest.php       | 19 ++++++++
 .../Core/Extension/ModuleHandlerTest.php      |  6 +++
 ...dule_implements_alter_test_legacy.info.yml |  6 +++
 ...module_implements_alter_test_legacy.module | 20 +++++++++
 15 files changed, 180 insertions(+), 62 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Hook/Attribute/LegacyModuleImplementsAlter.php
 create mode 100644 core/modules/system/tests/modules/module_implements_alter_test/module_implements_alter_test.implementations.inc
 create mode 100644 core/modules/system/tests/modules/module_implements_alter_test/module_implements_alter_test.info.yml
 create mode 100644 core/modules/system/tests/modules/module_implements_alter_test/module_implements_alter_test.module
 create mode 100644 core/tests/Drupal/Tests/Core/Extension/modules/module_implements_alter_test_legacy/module_implements_alter_test_legacy.info.yml
 create mode 100644 core/tests/Drupal/Tests/Core/Extension/modules/module_implements_alter_test_legacy/module_implements_alter_test_legacy.module

diff --git a/core/core.api.php b/core/core.api.php
index 0500a3c58aec..0dbec836754e 100644
--- a/core/core.api.php
+++ b/core/core.api.php
@@ -1657,6 +1657,7 @@
  * Legacy meta hooks:
  * - hook_hook_info()
  * - hook_module_implements_alter()
+ * @see https://www.drupal.org/node/3496788
  *
  * Install hooks:
  * - hook_install()
diff --git a/core/lib/Drupal/Core/Extension/module.api.php b/core/lib/Drupal/Core/Extension/module.api.php
index 765992e0606d..258507d6d7cc 100644
--- a/core/lib/Drupal/Core/Extension/module.api.php
+++ b/core/lib/Drupal/Core/Extension/module.api.php
@@ -94,6 +94,14 @@ function hook_hook_info(): array {
 /**
  * Alter the registry of modules implementing a hook.
  *
+ * This hook will be removed in 12.0.0.
+ * It has been intentionally not deprecated because custom code and contributed
+ * modules will still need to maintain implementations with the #[LegacyHook]
+ * attribute in order to support drupal versions older than 11.2.0.
+ *
+ * @link https://www.drupal.org/node/3496788
+ *
+ *
  * Only procedural implementations are supported for this hook.
  *
  * This hook is invoked in \Drupal::moduleHandler()->getImplementationInfo().
@@ -115,6 +123,8 @@ function hook_hook_info(): array {
  *   file named $module.$group.inc.
  * @param string $hook
  *   The name of the module hook being implemented.
+ *
+ * @see https://www.drupal.org/node/3496788
  */
 function hook_module_implements_alter(&$implementations, $hook) {
   if ($hook == 'form_alter') {
diff --git a/core/lib/Drupal/Core/Hook/Attribute/LegacyModuleImplementsAlter.php b/core/lib/Drupal/Core/Hook/Attribute/LegacyModuleImplementsAlter.php
new file mode 100644
index 000000000000..8b756973b38e
--- /dev/null
+++ b/core/lib/Drupal/Core/Hook/Attribute/LegacyModuleImplementsAlter.php
@@ -0,0 +1,26 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Core\Hook\Attribute;
+
+/**
+ * Defines a LegacyModuleImplementsAlter attribute object.
+ *
+ * This allows contrib and core to maintain legacy hook_module_implements_alter
+ * alongside the new attribute-based ordering. This means that a contrib module
+ * can simultaneously support Drupal 11.2 and older versions of Drupal.
+ *
+ * Marking hook_module_implements_alter as #LegacyModuleImplementsAlter will
+ * prevent hook_module_implements_alter from running when attribute-based
+ * ordering is available.
+ *
+ * On older versions of Drupal which are not aware of attribute-based ordering,
+ * only the legacy hook implementation is executed.
+ *
+ * For more information, see https://www.drupal.org/node/3496788.
+ *
+ * @internal
+ */
+#[\Attribute(\Attribute::TARGET_FUNCTION)]
+class LegacyModuleImplementsAlter {}
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index efa58463775f..34b2dd7b6dd2 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Extension\ProceduralCall;
 use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\Core\Hook\Attribute\LegacyModuleImplementsAlter;
 use Drupal\Core\Hook\Attribute\ReOrderHook;
 use Drupal\Core\Hook\Attribute\RemoveHook;
 use Drupal\Core\Hook\Attribute\StopProceduralHookScan;
@@ -380,7 +381,7 @@ 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)) {
+            if (!StaticReflectionParser::hasAttribute($attributes, LegacyHook::class) && preg_match($module_preg, $function, $matches) && !StaticReflectionParser::hasAttribute($attributes, LegacyModuleImplementsAlter::class)) {
               $implementations[] = ['function' => $function, 'module' => $matches['module'], 'hook' => $matches['hook']];
             }
           }
@@ -437,6 +438,8 @@ protected function addProceduralImplementation(\SplFileInfo $fileinfo, string $h
       $this->hookInfo[] = $function;
     }
     if ($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;
     }
     if ($fileinfo->getExtension() !== 'module') {
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 7eaeca68fb66..cc4d26756d1d 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/module_implements_alter_test/module_implements_alter_test.implementations.inc b/core/modules/system/tests/modules/module_implements_alter_test/module_implements_alter_test.implementations.inc
new file mode 100644
index 000000000000..7b6bac4ae95d
--- /dev/null
+++ b/core/modules/system/tests/modules/module_implements_alter_test/module_implements_alter_test.implementations.inc
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @file
+ * Include file for test module.
+ */
+
+declare(strict_types=1);
+
+/**
+ * Implements hook_altered_test_hook().
+ *
+ * @see module_implements_alter_test_module_implements_alter()
+ */
+function module_implements_alter_test_altered_test_hook(): string {
+  return __FUNCTION__;
+}
diff --git a/core/modules/system/tests/modules/module_implements_alter_test/module_implements_alter_test.info.yml b/core/modules/system/tests/modules/module_implements_alter_test/module_implements_alter_test.info.yml
new file mode 100644
index 000000000000..25995b17cd9a
--- /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 000000000000..6e7452988600
--- /dev/null
+++ b/core/modules/system/tests/modules/module_implements_alter_test/module_implements_alter_test.module
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Module file for test module.
+ */
+
+declare(strict_types=1);
+
+function test_auto_include(): void {}
+
+/**
+ * Implements hook_module_implements_alter().
+ *
+ * @see \Drupal\system\Tests\Module\ModuleImplementsAlterTest::testModuleImplementsAlter()
+ * @see module_implements_alter_test_module_implements_alter()
+ */
+function module_implements_alter_test_module_implements_alter(&$implementations, $hook): void {
+  if ($hook === 'altered_test_hook') {
+    // Add a hook implementation, that will be found in
+    // module_implements_alter_test.implementation.inc.
+    $implementations['module_implements_alter_test'] = 'implementations';
+  }
+  if ($hook === 'unimplemented_test_hook') {
+    // Add the non-existing function module_implements_alter_test_unimplemented_test_hook(). This
+    // should cause an exception to be thrown in
+    // \Drupal\Core\Extension\ModuleHandler::buildImplementationInfo('unimplemented_test_hook').
+    $implementations['module_implements_alter_test'] = FALSE;
+  }
+
+  // For
+  // \Drupal::moduleHandler()->alter(['drupal_alter', 'drupal_alter_foo'], ...),
+  // make the block module implementations run after all the other modules. Note
+  // that when \Drupal::moduleHandler->alter() is called with an array of types,
+  // the first type is considered primary and controls the module order.
+  if ($hook == 'drupal_alter_alter' && isset($implementations['block'])) {
+    $group = $implementations['block'];
+    unset($implementations['block']);
+    $implementations['block'] = $group;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/module_test/module_test.module b/core/modules/system/tests/modules/module_test/module_test.module
index b7f320a35e3c..35561f09c8a0 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 18217579ad95..0ff2115214f4 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/tests/Drupal/KernelTests/Core/Extension/ModuleImplementsAlterTest.php b/core/tests/Drupal/KernelTests/Core/Extension/ModuleImplementsAlterTest.php
index ef0022b1bfaf..2e9447c49913 100644
--- a/core/tests/Drupal/KernelTests/Core/Extension/ModuleImplementsAlterTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Extension/ModuleImplementsAlterTest.php
@@ -10,6 +10,8 @@
  * Tests hook_module_implements_alter().
  *
  * @group Module
+ *
+ * @group legacy
  */
 class ModuleImplementsAlterTest extends KernelTestBase {
 
@@ -22,7 +24,7 @@ class ModuleImplementsAlterTest extends KernelTestBase {
    * Tests hook_module_implements_alter() adding an implementation.
    *
    * @see \Drupal\Core\Extension\ModuleHandler::buildImplementationInfo()
-   * @see module_test_module_implements_alter()
+   * @see module_implements_alter_test_module_implements_alter()
    */
   public function testModuleImplementsAlter(): void {
 
@@ -32,38 +34,33 @@ public function testModuleImplementsAlter(): void {
 
     $this->assertSame(\Drupal::moduleHandler(), $module_handler, 'Module handler instance is still the same.');
 
-    // Install the module_test module.
-    \Drupal::service('module_installer')->install(['module_test']);
+    // Install the module_implements_alter_test module.
+    \Drupal::service('module_installer')->install(['module_implements_alter_test']);
 
     // Assert that the \Drupal::moduleHandler() instance has been replaced.
     $this->assertNotSame(\Drupal::moduleHandler(), $module_handler, 'The \Drupal::moduleHandler() instance has been replaced during \Drupal::moduleHandler()->install().');
 
-    // Assert that module_test.module is now included.
-    $this->assertTrue(function_exists('module_test_modules_installed'),
-      'The file module_test.module was successfully included.');
-
-    $this->assertArrayHasKey('module_test', \Drupal::moduleHandler()->getModuleList());
+    // Assert that module_implements_alter_test.module is now included.
+    $this->assertTrue(function_exists('test_auto_include'),
+    'The file module_implements_alter_test.module was successfully included.');
 
-    $this->assertTrue(\Drupal::moduleHandler()->hasImplementations('modules_installed', 'module_test'),
-      'module_test implements hook_modules_installed().');
+    $this->assertTrue(\Drupal::moduleHandler()->hasImplementations('module_implements_alter', 'module_implements_alter_test'),
+    'module_implements_alter_test implements hook_module_implements_alter().');
 
-    $this->assertTrue(\Drupal::moduleHandler()->hasImplementations('module_implements_alter', 'module_test'),
-      'module_test implements hook_module_implements_alter().');
-
-    // Assert that module_test.implementations.inc is not included yet.
-    $this->assertFalse(function_exists('module_test_altered_test_hook'),
-      'The file module_test.implementations.inc is not included yet.');
+    // Assert that module_implements_alter_test.implementations.inc is not included yet.
+    $this->assertFalse(function_exists('module_implements_alter_test_altered_test_hook'),
+      'The file module_implements_alter_test.implementations.inc is not included yet.');
 
     // Trigger hook discovery for hook_altered_test_hook().
-    // Assert that module_test_module_implements_alter(*, 'altered_test_hook')
+    // Assert that module_implements_alter_test_module_implements_alter(*, 'altered_test_hook')
     // has added an implementation.
-    $this->assertTrue(\Drupal::moduleHandler()->hasImplementations('altered_test_hook', 'module_test'),
-      'module_test implements hook_altered_test_hook().');
+    $this->assertTrue(\Drupal::moduleHandler()->hasImplementations('altered_test_hook', 'module_implements_alter_test'),
+      'module_implements_alter_test implements hook_altered_test_hook().');
+
+    // Assert that module_implements_alter_test.implementations.inc was included as part of the process.
+    $this->assertTrue(function_exists('module_implements_alter_test_altered_test_hook'),
+      'The file module_implements_alter_test.implementations.inc was included.');
 
-    // Assert that module_test.implementations.inc was included as part of the
-    // process.
-    $this->assertTrue(function_exists('module_test_altered_test_hook'),
-      'The file module_test.implementations.inc was included.');
   }
 
 }
diff --git a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php
index 9c3e6978d3d8..10765f3a084b 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.
    *
diff --git a/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php b/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php
index 500973836ede..143c56c542f8 100644
--- a/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php
+++ b/core/tests/Drupal/Tests/Core/Extension/ModuleHandlerTest.php
@@ -79,8 +79,11 @@ public function testLoadModule(): void {
    * Tests loading all modules.
    *
    * @covers ::loadAll
+   *
+   * @group legacy
    */
   public function testLoadAllModules(): void {
+    $this->expectDeprecation('module_handler_test_all1_module_implements_alter without a #[LegacyModuleImplementsAlter] attribute is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. See https://www.drupal.org/node/3496788');
     $module_handler = $this->getModuleHandler();
     $module_handler->addModule('module_handler_test_all1', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all1');
     $module_handler->addModule('module_handler_test_all2', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all2');
@@ -326,8 +329,11 @@ public function testImplementsHookModuleEnabled(): void {
    * Tests invoke all.
    *
    * @covers ::invokeAll
+   *
+   * @group legacy
    */
   public function testInvokeAll(): void {
+    $this->expectDeprecation('module_handler_test_all1_module_implements_alter without a #[LegacyModuleImplementsAlter] attribute is deprecated in drupal:11.2.0 and removed in drupal:12.0.0. See https://www.drupal.org/node/3496788');
     $module_handler = $this->getModuleHandler();
     $module_handler->addModule('module_handler_test_all1', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all1');
     $module_handler->addModule('module_handler_test_all2', 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all2');
diff --git a/core/tests/Drupal/Tests/Core/Extension/modules/module_implements_alter_test_legacy/module_implements_alter_test_legacy.info.yml b/core/tests/Drupal/Tests/Core/Extension/modules/module_implements_alter_test_legacy/module_implements_alter_test_legacy.info.yml
new file mode 100644
index 000000000000..e286a4ffac38
--- /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 000000000000..355b56169497
--- /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;
+}
-- 
GitLab


From 208030b3c1241cc3b7835f9f6e42f6dd05baab9b Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Wed, 26 Feb 2025 16:32:06 -0500
Subject: [PATCH 124/173] Static

---
 core/lib/Drupal/Core/Extension/ModuleHandler.php | 2 +-
 core/lib/Drupal/Core/Hook/HookCollectorPass.php  | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index b120c85d4fc8..67d7b7455014 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -436,7 +436,7 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
       $extra_modules = FALSE;
       $extra_listeners = [];
       if (isset($extra_types)) {
-        $extra_hooks = array_map(fn ($x) => $x . '_alter', $extra_types);
+        $extra_hooks = array_map(static fn ($x) => $x . '_alter', $extra_types);
         // First get the listeners implementing extra hooks.
         foreach ($extra_hooks as $extra_hook) {
           $hook_listeners = $this->findListenersForAlter($extra_hook, $hook_listeners, $extra_modules);
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 34b2dd7b6dd2..aa2f382311f8 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -252,7 +252,7 @@ protected static function reOrderImplementations(ContainerBuilder $container, ar
         // Verify the correct structure of
         // $hookOrderOperation->order->classesAndMethods and create specifiers
         // for HookPriority::change() while at it.
-        $otherSpecifiers = array_map(fn ($pair) => is_array($pair) ? $pair[0] . '::' . $pair[1] : throw new \LogicException('classesAndMethods needs to be an array of arrays'), $hookOrderOperation->order->classesAndMethods);
+        $otherSpecifiers = array_map(static fn ($pair) => is_array($pair) ? $pair[0] . '::' . $pair[1] : throw new \LogicException('classesAndMethods needs to be an array of arrays'), $hookOrderOperation->order->classesAndMethods);
         // Collect classes and methods for
         // self::registerComplexHookImplementations().
         $classesAndMethods = $hookOrderOperation->order->classesAndMethods;
@@ -303,7 +303,7 @@ protected static function reOrderImplementations(ContainerBuilder $container, ar
    *   @see https://www.drupal.org/project/drupal/issues/3481778
    */
   public static function collectAllHookImplementations(array $module_filenames, ?ContainerBuilder $container = NULL): static {
-    $modules = array_map(fn ($x) => preg_quote($x, '/'), array_keys($module_filenames));
+    $modules = array_map(static 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]+$))/';
@@ -517,7 +517,7 @@ protected static function getAttributeInstances(\ReflectionClass $reflectionClas
     foreach ($reflections as $reflection) {
       if ($reflection_attributes = $reflection->getAttributes(HookOperation::class, \ReflectionAttribute::IS_INSTANCEOF)) {
         $method = $reflection instanceof \ReflectionMethod ? $reflection->getName() : '__invoke';
-        $attributes[$method] = array_map(fn (\ReflectionAttribute $ra) => $ra->newInstance(), $reflection_attributes);
+        $attributes[$method] = array_map(static fn (\ReflectionAttribute $ra) => $ra->newInstance(), $reflection_attributes);
       }
     }
     return $attributes;
-- 
GitLab


From 9fd284d8f91a222a4ffefef0a1ff66a12d2b8688 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Wed, 26 Feb 2025 17:04:00 -0500
Subject: [PATCH 125/173] Remove compact

---
 core/lib/Drupal/Core/Hook/Attribute/Hook.php        | 2 +-
 core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php | 2 +-
 core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php  | 2 +-
 core/lib/Drupal/Core/Hook/HookCollectorPass.php     | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
index 4b77b1e5e301..47af2290434c 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -126,7 +126,7 @@ public function __construct(
     public ?string $module = NULL,
     Order|ComplexOrder|null $order = NULL,
   ) {
-    parent::__construct(... compact('hook', 'method', 'order'));
+    parent::__construct($hook, $method, order: $order);
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php b/core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php
index 398039ab7e7c..1f88da580f4b 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/ReOrderHook.php
@@ -35,7 +35,7 @@ public function __construct(
     string $method,
     Order|ComplexOrder $order,
   ) {
-    parent::__construct(... compact('hook', 'method', 'class', 'order'));
+    parent::__construct($hook, $method, $class, $order);
   }
 
 }
diff --git a/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php b/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
index 0f9306a301ff..5b4cca3b132a 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/RemoveHook.php
@@ -30,7 +30,7 @@ public function __construct(
     string $class,
     string $method,
   ) {
-    parent::__construct(... compact('hook', 'method', 'class'));
+    parent::__construct($hook, $method, $class);
   }
 
 }
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index aa2f382311f8..66142f5df0c5 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -107,7 +107,7 @@ public function process(ContainerBuilder $container): array {
               self::checkForProceduralOnlyHooks($hook, $class);
             }
             // Set properties on hook class that are needed for registration.
-            $hook->set(... compact('class', 'method', 'module'));
+            $hook->set($class, $module, $method);
             // Store a list of modules implementing hooks for simplifying
             // registration and hook_module_implements_alter execution.
             $legacyImplementationMap[$hook->hook][$hook->module] = '';
-- 
GitLab


From bb9e6394ce691e3d4a50a7d9795be78eeb0f422f Mon Sep 17 00:00:00 2001
From: nicxvan <29861-nicxvan@users.noreply.drupalcode.org>
Date: Fri, 28 Feb 2025 03:44:32 +0000
Subject: [PATCH 126/173] Correct comments

---
 core/lib/Drupal/Core/Extension/module.api.php                | 5 +++--
 .../Core/Hook/Attribute/LegacyModuleImplementsAlter.php      | 2 +-
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/core/lib/Drupal/Core/Extension/module.api.php b/core/lib/Drupal/Core/Extension/module.api.php
index 258507d6d7cc..d44b3d4f1c2f 100644
--- a/core/lib/Drupal/Core/Extension/module.api.php
+++ b/core/lib/Drupal/Core/Extension/module.api.php
@@ -96,8 +96,9 @@ function hook_hook_info(): array {
  *
  * This hook will be removed in 12.0.0.
  * It has been intentionally not deprecated because custom code and contributed
- * modules will still need to maintain implementations with the #[LegacyHook]
- * attribute in order to support drupal versions older than 11.2.0.
+ * modules will still need to maintain implementations with the
+ * #[LegacyModuleImplementsAlter] attribute in order to support drupal versions
+ * older than 11.2.0.
  *
  * @link https://www.drupal.org/node/3496788
  *
diff --git a/core/lib/Drupal/Core/Hook/Attribute/LegacyModuleImplementsAlter.php b/core/lib/Drupal/Core/Hook/Attribute/LegacyModuleImplementsAlter.php
index 8b756973b38e..0940fdc012bd 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/LegacyModuleImplementsAlter.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/LegacyModuleImplementsAlter.php
@@ -5,7 +5,7 @@
 namespace Drupal\Core\Hook\Attribute;
 
 /**
- * Defines a LegacyModuleImplementsAlter attribute object.
+ * Defines a LegacyModuleImplementsAlter object.
  *
  * This allows contrib and core to maintain legacy hook_module_implements_alter
  * alongside the new attribute-based ordering. This means that a contrib module
-- 
GitLab


From 950a2dc4a9bf7c171138fcc5fe9af8de6ea30949 Mon Sep 17 00:00:00 2001
From: nicxvan <29861-nicxvan@users.noreply.drupalcode.org>
Date: Fri, 28 Feb 2025 14:31:43 +0000
Subject: [PATCH 127/173] Typing

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 66142f5df0c5..e50d64ddea40 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -478,7 +478,7 @@ public function getImplementations($paths): array {
   /**
    * Checks for hooks which can't be supported in classes.
    *
-   * @param \Drupal\Core\Hook\Hook $hook
+   * @param \Drupal\Core\Hook\Attribute\Hook $hook
    *   The hook to check.
    * @param class-string $class
    *   The class the hook is implemented on.
@@ -507,8 +507,8 @@ public static function checkForProceduralOnlyHooks(Hook $hook, string $class): v
    * @param \ReflectionClass $reflectionClass
    *   A reflected class.
    *
-   * @return array
-   *   A list of Hook attribute instances.
+   * @return array<string, list<\Drupal\Core\Hook\HookOperation>>
+   *   Lists of Hook attribute instances by method name.
    */
   protected static function getAttributeInstances(\ReflectionClass $reflectionClass): array {
     $attributes = [];
-- 
GitLab


From dff7fd5bc0e52f0164476d30e90c5c35898dfb38 Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Fri, 28 Feb 2025 22:26:06 -0500
Subject: [PATCH 128/173] Rename hook to hook attribute and fix several
 variables

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 44 +++++++++----------
 1 file changed, 22 insertions(+), 22 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index e50d64ddea40..7f06624cc649 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -90,33 +90,33 @@ public function process(ContainerBuilder $container): array {
 
     // These attributes need to be processed after all hooks have been
     // processed.
-    $process_after = [
+    $processAfter = [
       RemoveHook::class => [],
       ReOrderHook::class => [],
     ];
     foreach (array_keys($container->getParameter('container.modules')) as $module) {
       foreach ($collector->moduleHooks[$module] ?? [] as $class => $methods) {
         foreach ($methods as $method => $hooks) {
-          foreach ($hooks as $hook) {
-            assert($hook instanceof HookOperation);
-            if (isset($process_after[get_class($hook)])) {
-              $process_after[get_class($hook)][] = $hook;
+          foreach ($hooks as $hookAttribute) {
+            assert($hookAttribute instanceof HookOperation);
+            if (isset($processAfter[get_class($hookAttribute)])) {
+              $processAfter[get_class($hookAttribute)][] = $hookAttribute;
               continue;
             }
             if ($class !== ProceduralCall::class) {
-              self::checkForProceduralOnlyHooks($hook, $class);
+              self::checkForProceduralOnlyHooks($hookAttribute, $class);
             }
             // Set properties on hook class that are needed for registration.
-            $hook->set($class, $module, $method);
+            $hookAttribute->set($class, $module, $method);
             // Store a list of modules implementing hooks for simplifying
             // registration and hook_module_implements_alter execution.
-            $legacyImplementationMap[$hook->hook][$hook->module] = '';
+            $legacyImplementationMap[$hookAttribute->hook][$hookAttribute->module] = '';
             // Store the implementation details for registering the hook.
-            $implementations[$hook->hook][$hook->module][$class][$hook->method] = $hook->method;
+            $implementations[$hookAttribute->hook][$hookAttribute->module][$class][$hookAttribute->method] = $hookAttribute->method;
             // Reverse lookup for modules implementing hooks.
-            $moduleFinder[$class][$hook->method] = $hook->module;
-            if ($hook->order) {
-              $hookOrderOperations[] = $hook;
+            $moduleFinder[$class][$hookAttribute->method] = $hookAttribute->module;
+            if ($hookAttribute->order) {
+              $hookOrderOperations[] = $hookAttribute;
             }
           }
         }
@@ -127,7 +127,7 @@ public function process(ContainerBuilder $container): array {
     // registering the hooks. This must happen after all collection, but before
     // registration to ensure the hook it is removing has already been
     // discovered.
-    foreach ($process_after[RemoveHook::class] as $removeHook) {
+    foreach ($processAfter[RemoveHook::class] as $removeHook) {
       if ($module = ($moduleFinder[$removeHook->class][$removeHook->method] ?? '')) {
         unset($legacyImplementationMap[$removeHook->hook][$module]);
         unset($implementations[$removeHook->hook][$module][$removeHook->class][$removeHook->method]);
@@ -138,7 +138,7 @@ public function process(ContainerBuilder $container): array {
     // before registering the hooks. This must happen after all collection,
     // but before registration to ensure this ordering directive takes
     // precedence.
-    foreach ($process_after[ReOrderHook::class] as $reOrderHook) {
+    foreach ($processAfter[ReOrderHook::class] as $reOrderHook) {
       $hookOrderOperations[] = $reOrderHook;
     }
 
@@ -256,9 +256,9 @@ protected static function reOrderImplementations(ContainerBuilder $container, ar
         // Collect classes and methods for
         // self::registerComplexHookImplementations().
         $classesAndMethods = $hookOrderOperation->order->classesAndMethods;
-        foreach ($hookOrderOperation->order->modules as $modules) {
+        foreach ($hookOrderOperation->order->modules as $module) {
           foreach ($hooks as $hook) {
-            foreach ($implementations[$hook][$modules] ?? [] as $class => $methods) {
+            foreach ($implementations[$hook][$module] ?? [] as $class => $methods) {
               foreach ($methods as $method) {
                 $classesAndMethods[] = [$class, $method];
                 $otherSpecifiers[] = "$class::$method";
@@ -478,12 +478,12 @@ public function getImplementations($paths): array {
   /**
    * 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 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',
@@ -496,8 +496,8 @@ 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.");
     }
   }
 
@@ -515,9 +515,9 @@ protected static function getAttributeInstances(\ReflectionClass $reflectionClas
     $reflections = $reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC);
     $reflections[] = $reflectionClass;
     foreach ($reflections as $reflection) {
-      if ($reflection_attributes = $reflection->getAttributes(HookOperation::class, \ReflectionAttribute::IS_INSTANCEOF)) {
+      if ($reflectionAttributes = $reflection->getAttributes(HookOperation::class, \ReflectionAttribute::IS_INSTANCEOF)) {
         $method = $reflection instanceof \ReflectionMethod ? $reflection->getName() : '__invoke';
-        $attributes[$method] = array_map(static fn (\ReflectionAttribute $ra) => $ra->newInstance(), $reflection_attributes);
+        $attributes[$method] = array_map(static fn (\ReflectionAttribute $ra) => $ra->newInstance(), $reflectionAttributes);
       }
     }
     return $attributes;
-- 
GitLab


From 5e117d03526e6a56a1b64781a1c5c91861e5dfb9 Mon Sep 17 00:00:00 2001
From: nicxvan <29861-nicxvan@users.noreply.drupalcode.org>
Date: Sat, 1 Mar 2025 03:30:16 +0000
Subject: [PATCH 129/173] Typing

---
 core/lib/Drupal/Core/Hook/ComplexOrder.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/ComplexOrder.php b/core/lib/Drupal/Core/Hook/ComplexOrder.php
index 766256dca14b..bf05cc79f344 100644
--- a/core/lib/Drupal/Core/Hook/ComplexOrder.php
+++ b/core/lib/Drupal/Core/Hook/ComplexOrder.php
@@ -27,9 +27,9 @@
   /**
    * Constructs a ComplexOrder object.
    *
-   * @param array $modules
+   * @param list<string> $modules
    *   A list of modules.
-   * @param array $classesAndMethods
+   * @param list<array{class-string, string}> $classesAndMethods
    *   A list of classes and methods, for example:
    *   @code
    *     [
-- 
GitLab


From 14ab5acaee42b5d9242b39a3643423cb31846af0 Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Sun, 2 Mar 2025 11:22:53 -0500
Subject: [PATCH 130/173] Handle removeHook legacyimplentationsmap

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 7f06624cc649..889674995642 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -129,8 +129,13 @@ public function process(ContainerBuilder $container): array {
     // discovered.
     foreach ($processAfter[RemoveHook::class] as $removeHook) {
       if ($module = ($moduleFinder[$removeHook->class][$removeHook->method] ?? '')) {
-        unset($legacyImplementationMap[$removeHook->hook][$module]);
         unset($implementations[$removeHook->hook][$module][$removeHook->class][$removeHook->method]);
+        // A module can implement a hook  more than one time so confirm no
+        // more implementations before removing from the
+        // $legacyImplementationMap.
+        if (!isset($implementations[$removeHook->hook][$module])) {
+          unset($legacyImplementationMap[$removeHook->hook][$module]);
+        }
       }
     }
 
-- 
GitLab


From 648d61a1abd440f75d003147c9cdd2c9444563a7 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Mon, 3 Mar 2025 09:36:30 -0500
Subject: [PATCH 131/173] Apply in scope typing and add hook check

---
 .../Drupal/Core/Extension/ModuleHandler.php   | 17 +++++---
 core/lib/Drupal/Core/Hook/ComplexOrder.php    |  2 +-
 .../Drupal/Core/Hook/HookCollectorPass.php    | 42 +++++++++++++------
 3 files changed, 43 insertions(+), 18 deletions(-)

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 67d7b7455014..71e74ecaf899 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -80,7 +80,7 @@ class ModuleHandler implements ModuleHandlerInterface {
    *   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 $orderedExtraTypes
+   * @param array<string, list<string>> $orderedExtraTypes
    *   A multidimensional array of hooks that have been ordered and the
    *   extra_types they have been ordered against. This is stored separately
    *   from $hookImplementationsMap to prevent ordering again since this set
@@ -89,7 +89,14 @@ class ModuleHandler implements ModuleHandlerInterface {
    * @see \Drupal\Core\DrupalKernel
    * @see \Drupal\Core\CoreServiceProvider
    */
-  public function __construct($root, array $module_list, protected EventDispatcherInterface $eventDispatcher, protected array $hookImplementationsMap, protected array $groupIncludes = [], protected array $orderedExtraTypes = []) {
+  public function __construct(
+    $root,
+    array $module_list,
+    protected EventDispatcherInterface $eventDispatcher,
+    protected array $hookImplementationsMap,
+    protected array $groupIncludes = [],
+    protected array $orderedExtraTypes = [],
+  ) {
     $this->root = $root;
     $this->moduleList = [];
     foreach ($module_list as $name => $module) {
@@ -598,12 +605,12 @@ protected function getHookListeners(string $hook): array {
    *
    * @param string $hook
    *   The extra hook or combination hook to check for.
-   * @param array $hook_listeners
+   * @param array<string, list<callable>> $hook_listeners
    *   Hook listeners for the current hook_alter.
-   * @param bool $extra_modules
+   * @param bool|null $extra_modules
    *   Whether there are extra modules to order.
    *
-   * @return array
+   * @return array<string, list<callable>>
    *   The hook listeners.
    */
   public function findListenersForAlter(string $hook, array $hook_listeners = [], ?bool &$extra_modules = NULL): array {
diff --git a/core/lib/Drupal/Core/Hook/ComplexOrder.php b/core/lib/Drupal/Core/Hook/ComplexOrder.php
index bf05cc79f344..a44e0bd2e815 100644
--- a/core/lib/Drupal/Core/Hook/ComplexOrder.php
+++ b/core/lib/Drupal/Core/Hook/ComplexOrder.php
@@ -37,7 +37,7 @@
    *       [Bar::class, 'someOtherMethod'],
    *     ]
    *   @endcode
-   * @param array $extraTypes
+   * @param list<string> $extraTypes
    *   A list of hooks to be ordered together. Ordering by attributes happens
    *   at build time by setting up the order of the listeners of a hook
    *   correctly. However, ModuleHandlerInterface::alter() can be called with
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 889674995642..e9a5950f92b0 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -63,11 +63,17 @@ class HookCollectorPass implements CompilerPassInterface {
    * A list of attributes for hook implementations.
    *
    * Keys are module, class and method. Values are Hook attributes.
+   *
+   * @var array<string, array<class-string, array<string, list<\Drupal\Core\Hook\HookOperation>>>>
    */
   protected array $moduleHooks = [];
 
   /**
    * {@inheritdoc}
+   *
+   * @return array<string, array<string, array<class-string, array<string, string>>>>
+   *   Hook implementation method names
+   *   keyed by hook, module, class and method.
    */
   public function process(ContainerBuilder $container): array {
     $collector = static::collectAllHookImplementations($container->getParameter('container.modules'), $container);
@@ -103,6 +109,11 @@ public function process(ContainerBuilder $container): array {
               $processAfter[get_class($hookAttribute)][] = $hookAttribute;
               continue;
             }
+            if (!($hookAttribute instanceof Hook)) {
+              // This is an unsupported attribute class, the code below would
+              // not work.
+              continue;
+            }
             if ($class !== ProceduralCall::class) {
               self::checkForProceduralOnlyHooks($hookAttribute, $class);
             }
@@ -234,16 +245,18 @@ protected static function registerImplementations(ContainerBuilder $container, H
    *
    * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
    *   The container.
-   * @param array $hookOrderOperations
+   * @param list<\Drupal\Core\Hook\HookOperation> $hookOrderOperations
    *   All attributes that contain ordering information.
    * @param array<string, list<string>> $orderExtraTypes
-   *   Extra types to order together with.
-   * @param array $implementations
-   *   Hook implementations.
-   * @param array $moduleFinder
-   *   An array keyed by the class and method of a hook implementation, value
-   *   is the module. This is not necessarily the same as the module the class
-   *   is in because the implementation might be on behalf of another module.
+   *   Lists of extra hooks to order together with, keyed by hook name.
+   * @param array<string, array<string, array<class-string, list<string>>>> $implementations
+   *   Hook implementations, as method names by hook, module and class.
+   * @param array<class-string, array<string, string>> $moduleFinder
+   *   Lookup map to find the module for each hook implementation.
+   *   Array keys are the class and method of the hook implementation, array
+   *   values are module names.
+   *   The module name can be different from the module the class is in,
+   *   because an implementation can be on behalf of another module.
    */
   protected static function reOrderImplementations(ContainerBuilder $container, array $hookOrderOperations, array $orderExtraTypes, array $implementations, array $moduleFinder): void {
     $hookPriority = new HookPriority($container);
@@ -257,7 +270,12 @@ protected static function reOrderImplementations(ContainerBuilder $container, ar
         // Verify the correct structure of
         // $hookOrderOperation->order->classesAndMethods and create specifiers
         // for HookPriority::change() while at it.
-        $otherSpecifiers = array_map(static fn ($pair) => is_array($pair) ? $pair[0] . '::' . $pair[1] : throw new \LogicException('classesAndMethods needs to be an array of arrays'), $hookOrderOperation->order->classesAndMethods);
+        $otherSpecifiers = array_map(
+          static fn ($pair) => is_array($pair)
+            ? $pair[0] . '::' . $pair[1]
+            : throw new \LogicException('classesAndMethods needs to be an array of arrays'),
+          $hookOrderOperation->order->classesAndMethods
+        );
         // Collect classes and methods for
         // self::registerComplexHookImplementations().
         $classesAndMethods = $hookOrderOperation->order->classesAndMethods;
@@ -557,10 +575,10 @@ protected static function addTagToDefinition(Definition $definition, string|int
    *
    * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
    *   The container.
-   * @param array $classesAndMethods
+   * @param list<array{class-string, string}> $classesAndMethods
    *   A list of class-and-method pairs.
-   * @param array $moduleFinder
-   *   A module finder array, see ::reOrderImplementations() for explanation.
+   * @param array<class-string, array<string, string>> $moduleFinder
+   *   Module names by class and method of hook implementations.
    * @param string $combinedHook
    *   A string made form list of hooks separated by :
    */
-- 
GitLab


From df6edab6e94280730e601347909656898ead3508 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Mon, 3 Mar 2025 09:55:25 -0500
Subject: [PATCH 132/173] Address comments on moduleFinder

---
 .../lib/Drupal/Core/Hook/HookCollectorPass.php | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index e9a5950f92b0..34cca6abb09d 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -125,7 +125,7 @@ public function process(ContainerBuilder $container): array {
             // Store the implementation details for registering the hook.
             $implementations[$hookAttribute->hook][$hookAttribute->module][$class][$hookAttribute->method] = $hookAttribute->method;
             // Reverse lookup for modules implementing hooks.
-            $moduleFinder[$class][$hookAttribute->method] = $hookAttribute->module;
+            $moduleFinder[$class][$hookAttribute->method][$hookAttribute->hook] = $hookAttribute->module;
             if ($hookAttribute->order) {
               $hookOrderOperations[] = $hookAttribute;
             }
@@ -139,7 +139,7 @@ public function process(ContainerBuilder $container): array {
     // registration to ensure the hook it is removing has already been
     // discovered.
     foreach ($processAfter[RemoveHook::class] as $removeHook) {
-      if ($module = ($moduleFinder[$removeHook->class][$removeHook->method] ?? '')) {
+      if ($module = ($moduleFinder[$removeHook->class][$removeHook->method][$removeHook->hook] ?? '')) {
         unset($implementations[$removeHook->hook][$module][$removeHook->class][$removeHook->method]);
         // A module can implement a hook  more than one time so confirm no
         // more implementations before removing from the
@@ -251,10 +251,10 @@ protected static function registerImplementations(ContainerBuilder $container, H
    *   Lists of extra hooks to order together with, keyed by hook name.
    * @param array<string, array<string, array<class-string, list<string>>>> $implementations
    *   Hook implementations, as method names by hook, module and class.
-   * @param array<class-string, array<string, string>> $moduleFinder
+   * @param array<class-string, array<array<string, string>>> $moduleFinder
    *   Lookup map to find the module for each hook implementation.
-   *   Array keys are the class and method of the hook implementation, array
-   *   values are module names.
+   *   Array keys are the class, method, and hook, array values are module
+   *   names.
    *   The module name can be different from the module the class is in,
    *   because an implementation can be on behalf of another module.
    */
@@ -577,8 +577,9 @@ protected static function addTagToDefinition(Definition $definition, string|int
    *   The container.
    * @param list<array{class-string, string}> $classesAndMethods
    *   A list of class-and-method pairs.
-   * @param array<class-string, array<string, string>> $moduleFinder
-   *   Module names by class and method of hook implementations.
+   * @param array<class-string, array<array<string, string>>> $moduleFinder
+   *   Array keys are the class, method, and hook, array values are module
+   *   names.
    * @param string $combinedHook
    *   A string made form list of hooks separated by :
    */
@@ -588,6 +589,9 @@ protected static function registerComplexHookImplementations(ContainerBuilder $c
     foreach ($classesAndMethods as [$class, $method]) {
       // Ordering against not installed modules is possible.
       if (isset($moduleFinder[$class][$method])) {
+        if (count(array_unique($moduleFinder[$class][$method])) > 1) {
+          throw new \LogicException('Complex ordering can only work when all implementations on a single method are for the same module.');
+        }
         $map[$combinedHook][$class][$method] = $moduleFinder[$class][$method];
         $priority = self::addTagToDefinition($container->findDefinition($class), $combinedHook, $method, $priority);
       }
-- 
GitLab


From df34d155cbf8294a8400cf4e647a2a16973b2310 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Mon, 3 Mar 2025 12:12:20 -0500
Subject: [PATCH 133/173] Reset module

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 34cca6abb09d..b27ac5603acd 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -271,9 +271,7 @@ protected static function reOrderImplementations(ContainerBuilder $container, ar
         // $hookOrderOperation->order->classesAndMethods and create specifiers
         // for HookPriority::change() while at it.
         $otherSpecifiers = array_map(
-          static fn ($pair) => is_array($pair)
-            ? $pair[0] . '::' . $pair[1]
-            : throw new \LogicException('classesAndMethods needs to be an array of arrays'),
+          static fn ($pair) => is_array($pair) ? $pair[0] . '::' . $pair[1] : throw new \LogicException('classesAndMethods needs to be an array of arrays'),
           $hookOrderOperation->order->classesAndMethods
         );
         // Collect classes and methods for
@@ -592,7 +590,7 @@ protected static function registerComplexHookImplementations(ContainerBuilder $c
         if (count(array_unique($moduleFinder[$class][$method])) > 1) {
           throw new \LogicException('Complex ordering can only work when all implementations on a single method are for the same module.');
         }
-        $map[$combinedHook][$class][$method] = $moduleFinder[$class][$method];
+        $map[$combinedHook][$class][$method] = reset($moduleFinder[$class][$method]);
         $priority = self::addTagToDefinition($container->findDefinition($class), $combinedHook, $method, $priority);
       }
     }
-- 
GitLab


From 81e9a6b3f87ddd452f9eaf3ae774e9cc5146ca92 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Mon, 3 Mar 2025 12:37:20 -0500
Subject: [PATCH 134/173] PHPSTAN and CS

---
 core/lib/Drupal/Core/Hook/Attribute/Hook.php              | 4 ++--
 core/lib/Drupal/Core/Hook/HookCollectorPass.php           | 4 ++--
 core/lib/Drupal/Core/Hook/HookOperation.php               | 2 +-
 core/lib/Drupal/Core/Hook/Order.php                       | 8 ++------
 .../tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php | 3 +++
 5 files changed, 10 insertions(+), 11 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/Attribute/Hook.php b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
index 47af2290434c..0a4c5b5cdec9 100644
--- a/core/lib/Drupal/Core/Hook/Attribute/Hook.php
+++ b/core/lib/Drupal/Core/Hook/Attribute/Hook.php
@@ -108,7 +108,7 @@ class Hook extends HookOperation {
    *
    * @param string $hook
    *   The short hook name, without the 'hook_' prefix.
-   * @param ?string $method
+   * @param string $method
    *   (optional) The method name. If this attribute is on a method, this
    *   parameter is not required. If this attribute is on a class and this
    *   parameter is omitted, the class must have an __invoke() method, which is
@@ -122,7 +122,7 @@ class Hook extends HookOperation {
    */
   public function __construct(
     string $hook,
-    ?string $method = '',
+    string $method = '',
     public ?string $module = NULL,
     Order|ComplexOrder|null $order = NULL,
   ) {
diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index b27ac5603acd..5047da9307c1 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -550,7 +550,7 @@ protected static function getAttributeInstances(\ReflectionClass $reflectionClas
    * @param \Symfony\Component\DependencyInjection\Definition $definition
    *   The service definition.
    * @param string|int $hook
-   *   The name of the hook
+   *   The name of the hook.
    * @param string $method
    *   The method.
    * @param int $priority
@@ -579,7 +579,7 @@ protected static function addTagToDefinition(Definition $definition, string|int
    *   Array keys are the class, method, and hook, array values are module
    *   names.
    * @param string $combinedHook
-   *   A string made form list of hooks separated by :
+   *   A string made form list of hooks separated by :.
    */
   protected static function registerComplexHookImplementations(ContainerBuilder $container, array $classesAndMethods, array $moduleFinder, string $combinedHook): void {
     $map = $container->getParameter('hook_implementations_map');
diff --git a/core/lib/Drupal/Core/Hook/HookOperation.php b/core/lib/Drupal/Core/Hook/HookOperation.php
index 6794a536552a..f212588a42c6 100644
--- a/core/lib/Drupal/Core/Hook/HookOperation.php
+++ b/core/lib/Drupal/Core/Hook/HookOperation.php
@@ -29,7 +29,7 @@ abstract class HookOperation {
   public function __construct(
     public string $hook,
     public string $method,
-    public ?string $class = '',
+    public ?string $class = NULL,
     public Order|ComplexOrder|null $order = NULL,
   ) {}
 
diff --git a/core/lib/Drupal/Core/Hook/Order.php b/core/lib/Drupal/Core/Hook/Order.php
index e8c8898d2a03..f26ef257db72 100644
--- a/core/lib/Drupal/Core/Hook/Order.php
+++ b/core/lib/Drupal/Core/Hook/Order.php
@@ -9,14 +9,10 @@
  */
 enum Order: int {
 
-  /**
-   * This implementation should fire first.
-   */
+  // This implementation should fire first.
   case First = 1;
 
-  /**
-   * This implementation should fire last.
-   */
+  // This implementation should fire last.
   case Last = 0;
 
 }
diff --git a/core/tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php b/core/tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php
index ddee181e3f62..36cbd21055f0 100644
--- a/core/tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php
+++ b/core/tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php
@@ -10,6 +10,9 @@
 use Drupal\Core\Hook\Order;
 use Drupal\Tests\UnitTestCase;
 
+/**
+ * Base class for testing HookPriority.
+ */
 abstract class HookPriorityTestBase extends UnitTestCase {
 
   /**
-- 
GitLab


From cc2b773f7febcff8e913713b318b73dc9f90f890 Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Mon, 3 Mar 2025 23:03:54 -0500
Subject: [PATCH 135/173] Correct array shape

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 5047da9307c1..859754bd9239 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -251,7 +251,7 @@ protected static function registerImplementations(ContainerBuilder $container, H
    *   Lists of extra hooks to order together with, keyed by hook name.
    * @param array<string, array<string, array<class-string, list<string>>>> $implementations
    *   Hook implementations, as method names by hook, module and class.
-   * @param array<class-string, array<array<string, string>>> $moduleFinder
+   * @param array<class-string, array<string, array<string, string>>> $moduleFinder
    *   Lookup map to find the module for each hook implementation.
    *   Array keys are the class, method, and hook, array values are module
    *   names.
@@ -575,7 +575,7 @@ protected static function addTagToDefinition(Definition $definition, string|int
    *   The container.
    * @param list<array{class-string, string}> $classesAndMethods
    *   A list of class-and-method pairs.
-   * @param array<class-string, array<array<string, string>>> $moduleFinder
+   * @param array<class-string, array<string, array<string, string>>> $moduleFinder
    *   Array keys are the class, method, and hook, array values are module
    *   names.
    * @param string $combinedHook
-- 
GitLab


From 432f2ed30fe7ba3105d149fabf4c3086a03c5454 Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Mon, 3 Mar 2025 23:25:40 -0500
Subject: [PATCH 136/173] Add comments

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 859754bd9239..b11e8e73e3ef 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -197,6 +197,10 @@ public function process(ContainerBuilder $container): array {
   protected static function registerImplementations(ContainerBuilder $container, HookCollectorPass $collector, array $implementations, array $legacyImplementationMap, array $orderExtraTypes): void {
     $container->register(ProceduralCall::class, ProceduralCall::class)
       ->addArgument($collector->includes);
+
+    // Gather includes for each hook_hook_info group.
+    // We store this in $groupIncludes so moduleHandler can ensure the files
+    // are included runtime when the hooks are invoked.
     $groupIncludes = [];
     foreach ($collector->hookInfo as $function) {
       foreach ($function() as $hook => $info) {
@@ -206,14 +210,19 @@ protected static function registerImplementations(ContainerBuilder $container, H
       }
     }
 
+    // Register all implementations.
     foreach ($legacyImplementationMap as $hook => $moduleImplements) {
       $extraHooks = $orderExtraTypes[$hook] ?? [];
       foreach ($extraHooks as $extraHook) {
         $moduleImplements += $legacyImplementationMap[$extraHook] ?? [];
       }
+      // Process all hook_module_implements_alter() for build time ordering.
       foreach ($collector->moduleImplementsAlters as $alter) {
         $alter($moduleImplements, $hook);
       }
+      // Start at 0 for the first hook. We decrease the priority after each
+      // hook that is registered. Symfony priorities run higher priorities
+      // first.
       $priority = 0;
       foreach ($moduleImplements as $module => $v) {
         foreach ($implementations[$hook][$module] ?? [] as $class => $method_hooks) {
@@ -234,6 +243,7 @@ protected static function registerImplementations(ContainerBuilder $container, H
       }
     }
 
+    // Pass necessary parameters to moduleHandler.
     $definition = $container->getDefinition('module_handler');
     $definition->setArgument('$groupIncludes', $groupIncludes);
     $definition->setArgument('$orderedExtraTypes', $orderExtraTypes);
-- 
GitLab


From e194ae30e918834ea60996298e1050fec308029b Mon Sep 17 00:00:00 2001
From: nicxvan <nic@nlightened.net>
Date: Tue, 4 Mar 2025 23:53:56 -0500
Subject: [PATCH 137/173] Modify removal check

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index b11e8e73e3ef..1b7fbf8381ee 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -140,11 +140,24 @@ public function process(ContainerBuilder $container): array {
     // discovered.
     foreach ($processAfter[RemoveHook::class] as $removeHook) {
       if ($module = ($moduleFinder[$removeHook->class][$removeHook->method][$removeHook->hook] ?? '')) {
+        // Remove the hook implementation for the defined class, method, and
+        // hook.
         unset($implementations[$removeHook->hook][$module][$removeHook->class][$removeHook->method]);
-        // A module can implement a hook  more than one time so confirm no
-        // more implementations before removing from the
+        // Check if the given module has implemented the hook on more than one
+        // method for the class.
+        // Hook removal is very rare so it is more efficient to do the check
+        // here.
+        if ($implementations[$removeHook->hook][$module][$removeHook->class]) {
+          // Remove the class from the implementation map if the hook has no
+          // more implementations.
+          unset($implementations[$removeHook->hook][$module][$removeHook->class]);
+        }
+        // A module can implement a hook on more than one class so we confirm
+        // there are no more implementations before removing from the
         // $legacyImplementationMap.
-        if (!isset($implementations[$removeHook->hook][$module])) {
+        // We do not need to clear further empty arrays since we handle this
+        // state before registering the hooks.
+        if (empty($implementations[$removeHook->hook][$module])) {
           unset($legacyImplementationMap[$removeHook->hook][$module]);
         }
       }
-- 
GitLab


From b287cdb17389de236cb3ef661aab67184c90ebb8 Mon Sep 17 00:00:00 2001
From: nlighteneddesign <nic@nlighteneddevelopment.com>
Date: Wed, 5 Mar 2025 16:48:11 -0500
Subject: [PATCH 138/173] Add comment to moduleImplements loop

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 1b7fbf8381ee..69d60d119400 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -226,6 +226,8 @@ protected static function registerImplementations(ContainerBuilder $container, H
     // Register all implementations.
     foreach ($legacyImplementationMap as $hook => $moduleImplements) {
       $extraHooks = $orderExtraTypes[$hook] ?? [];
+      // Add implementations to the array we pass to HMIA when the definition
+      // specifies that they should be ordered together.
       foreach ($extraHooks as $extraHook) {
         $moduleImplements += $legacyImplementationMap[$extraHook] ?? [];
       }
@@ -239,7 +241,7 @@ protected static function registerImplementations(ContainerBuilder $container, H
       $priority = 0;
       foreach ($moduleImplements as $module => $v) {
         foreach ($implementations[$hook][$module] ?? [] as $class => $method_hooks) {
-          if ($container->has($class)) {
+          if ($container->hasDefinition($class)) {
             $definition = $container->findDefinition($class);
           }
           else {
-- 
GitLab


From 681439a3c4b4ad73cb3883297725720055c03570 Mon Sep 17 00:00:00 2001
From: nicxvan <29861-nicxvan@users.noreply.drupalcode.org>
Date: Wed, 5 Mar 2025 22:35:38 +0000
Subject: [PATCH 139/173] Spelling

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 69d60d119400..13b6e1ea07a5 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -226,8 +226,8 @@ protected static function registerImplementations(ContainerBuilder $container, H
     // Register all implementations.
     foreach ($legacyImplementationMap as $hook => $moduleImplements) {
       $extraHooks = $orderExtraTypes[$hook] ?? [];
-      // Add implementations to the array we pass to HMIA when the definition
-      // specifies that they should be ordered together.
+      // Add implementations to the array we pass to legacy ordering
+      // when the definition specifies that they should be ordered together.
       foreach ($extraHooks as $extraHook) {
         $moduleImplements += $legacyImplementationMap[$extraHook] ?? [];
       }
-- 
GitLab


From 4db68b3bacb3e906c8cfa85d7c07e8ae89f5c498 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Tue, 4 Mar 2025 00:04:04 +0100
Subject: [PATCH 140/173] OPTIONAL: Doc return comment back to one line.

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 13b6e1ea07a5..3d2ac093de85 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -72,8 +72,7 @@ class HookCollectorPass implements CompilerPassInterface {
    * {@inheritdoc}
    *
    * @return array<string, array<string, array<class-string, array<string, string>>>>
-   *   Hook implementation method names
-   *   keyed by hook, module, class and method.
+   *   Hook implementation method names keyed by hook, module, class and method.
    */
   public function process(ContainerBuilder $container): array {
     $collector = static::collectAllHookImplementations($container->getParameter('container.modules'), $container);
-- 
GitLab


From ba613bbfb726c5002d94a5a618cec888517cdf46 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Tue, 4 Mar 2025 00:32:35 +0100
Subject: [PATCH 141/173] Drop parens around instanceof.

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 3d2ac093de85..c62f12cb5d57 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -108,7 +108,7 @@ public function process(ContainerBuilder $container): array {
               $processAfter[get_class($hookAttribute)][] = $hookAttribute;
               continue;
             }
-            if (!($hookAttribute instanceof Hook)) {
+            if (!$hookAttribute instanceof Hook) {
               // This is an unsupported attribute class, the code below would
               // not work.
               continue;
-- 
GitLab


From ac14f5c50d2e2b6c0f95ad2a22435ec5942d5160 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Sun, 2 Mar 2025 01:05:27 +0100
Subject: [PATCH 142/173] Add param type for getImplementations().

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index c62f12cb5d57..89bff8fdaf14 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -514,7 +514,7 @@ public function loadAllIncludes(): void {
    *
    * @internal
    */
-  public function getImplementations($paths): array {
+  public function getImplementations(array $paths): array {
     $container = new ContainerBuilder();
     $container->setParameter('container.modules', $paths);
     return $this->process($container);
-- 
GitLab


From 74d4a0f58e9a9f484ad4ea62a3997db2d0ae8878 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Sun, 2 Mar 2025 01:34:05 +0100
Subject: [PATCH 143/173] Use regular 'See' for link in doc.

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 89bff8fdaf14..79397f271b4d 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -510,7 +510,7 @@ public function loadAllIncludes(): void {
    * This method is only to be used by ModuleHandler.
    *
    * @todo remove when ModuleHandler::add() is removed.
-   * @see https://www.drupal.org/project/drupal/issues/3481778
+   *   See https://www.drupal.org/project/drupal/issues/3481778
    *
    * @internal
    */
-- 
GitLab


From 4773eb6f8e447b2e02621a2ad6ba2bab84274ecb Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Sun, 2 Mar 2025 18:02:58 +0100
Subject: [PATCH 144/173] Remove empty arrays from $implementations.

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 29 +++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 79397f271b4d..57a2ec3e1727 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -161,6 +161,7 @@ public function process(ContainerBuilder $container): array {
         }
       }
     }
+    $implementations = static::removeEmptyArraysRecursively($implementations);
 
     // Loop over all ReOrderHook attributes and gather order information
     // before registering the hooks. This must happen after all collection,
@@ -621,4 +622,32 @@ protected static function registerComplexHookImplementations(ContainerBuilder $c
     $container->setParameter('hook_implementations_map', $map);
   }
 
+  /**
+   * Removes empty sub-arrays recursively, preserving keys.
+   *
+   * This is different from NestedArray::filter() or regular array_filter():
+   *   - It only removes empty arrays, not other empty-ish values.
+   *   - It reliably removes empty parent arrays, after all children have been
+   *     removed. See https://www.drupal.org/project/drupal/issues/3381640.
+   *
+   * @param array $array
+   *   Array which may or may not contain other arrays as values.
+   *
+   * @return array
+   *   Filtered array, with empty arrays removed.
+   *
+   * @see \Drupal\Component\Utility\NestedArray::filter()
+   */
+  protected static function removeEmptyArraysRecursively(array $array): array {
+    foreach ($array as $key => $value) {
+      if (is_array($value)) {
+        $value = self::removeEmptyArraysRecursively($value);
+        if ($value === []) {
+          unset($array[$key]);
+        }
+      }
+    }
+    return $array;
+  }
+
 }
-- 
GitLab


From bfbb0bd2045ccfe170bc6bcee16f60b22d590078 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Sun, 2 Mar 2025 17:39:50 +0100
Subject: [PATCH 145/173] Calculate $legacyImplementationsMap after the hook
 removal.

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 31 +++++--------------
 1 file changed, 8 insertions(+), 23 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 57a2ec3e1727..864e356f5b70 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -80,9 +80,6 @@ public function process(ContainerBuilder $container): array {
     // List of modules implementing hooks with the implementation details.
     $implementations = [];
 
-    // List of hooks and modules formatted for hook_module_implements_alter().
-    $legacyImplementationMap = [];
-
     // Hooks that should be ordered together when extra types are involved.
     $orderExtraTypes = [];
 
@@ -118,9 +115,6 @@ public function process(ContainerBuilder $container): array {
             }
             // Set properties on hook class that are needed for registration.
             $hookAttribute->set($class, $module, $method);
-            // Store a list of modules implementing hooks for simplifying
-            // registration and hook_module_implements_alter execution.
-            $legacyImplementationMap[$hookAttribute->hook][$hookAttribute->module] = '';
             // Store the implementation details for registering the hook.
             $implementations[$hookAttribute->hook][$hookAttribute->module][$class][$hookAttribute->method] = $hookAttribute->method;
             // Reverse lookup for modules implementing hooks.
@@ -142,27 +136,18 @@ public function process(ContainerBuilder $container): array {
         // Remove the hook implementation for the defined class, method, and
         // hook.
         unset($implementations[$removeHook->hook][$module][$removeHook->class][$removeHook->method]);
-        // Check if the given module has implemented the hook on more than one
-        // method for the class.
-        // Hook removal is very rare so it is more efficient to do the check
-        // here.
-        if ($implementations[$removeHook->hook][$module][$removeHook->class]) {
-          // Remove the class from the implementation map if the hook has no
-          // more implementations.
-          unset($implementations[$removeHook->hook][$module][$removeHook->class]);
-        }
-        // A module can implement a hook on more than one class so we confirm
-        // there are no more implementations before removing from the
-        // $legacyImplementationMap.
-        // We do not need to clear further empty arrays since we handle this
-        // state before registering the hooks.
-        if (empty($implementations[$removeHook->hook][$module])) {
-          unset($legacyImplementationMap[$removeHook->hook][$module]);
-        }
       }
     }
     $implementations = static::removeEmptyArraysRecursively($implementations);
 
+    // List of hooks and modules formatted for hook_module_implements_alter().
+    $legacyImplementationMap = [];
+    foreach ($implementations as $hook => $implementationsByModule) {
+      foreach ($implementationsByModule as $module => $implementationsByClass) {
+        $legacyImplementationMap[$hook][$module] = '';
+      }
+    }
+
     // Loop over all ReOrderHook attributes and gather order information
     // before registering the hooks. This must happen after all collection,
     // but before registration to ensure this ordering directive takes
-- 
GitLab


From 423d4a9a6fe7ba40cff5d48068bd7c465c515322 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Sun, 2 Mar 2025 21:59:55 +0100
Subject: [PATCH 146/173] Calculate $legacyImplementationMap later, in
 registerImplementations().

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 23 ++++++++-----------
 1 file changed, 10 insertions(+), 13 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 864e356f5b70..451323b98830 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -140,14 +140,6 @@ public function process(ContainerBuilder $container): array {
     }
     $implementations = static::removeEmptyArraysRecursively($implementations);
 
-    // List of hooks and modules formatted for hook_module_implements_alter().
-    $legacyImplementationMap = [];
-    foreach ($implementations as $hook => $implementationsByModule) {
-      foreach ($implementationsByModule as $module => $implementationsByClass) {
-        $legacyImplementationMap[$hook][$module] = '';
-      }
-    }
-
     // Loop over all ReOrderHook attributes and gather order information
     // before registering the hooks. This must happen after all collection,
     // but before registration to ensure this ordering directive takes
@@ -170,7 +162,7 @@ public function process(ContainerBuilder $container): array {
     // is removed.
     // @see https://www.drupal.org/project/drupal/issues/3481778
     if (count($container->getDefinitions()) > 1) {
-      static::registerImplementations($container, $collector, $implementations, $legacyImplementationMap, $orderExtraTypes);
+      static::registerImplementations($container, $collector, $implementations, $orderExtraTypes);
       static::reOrderImplementations($container, $hookOrderOperations, $orderExtraTypes, $implementations, $moduleFinder);
     }
     return $implementations;
@@ -187,12 +179,10 @@ public function process(ContainerBuilder $container): array {
    *   The collector.
    * @param array<string, array<string, array<class-string, list<string>>>> $implementations
    *   All implementations, as method names keyed by hook, module and class.
-   * @param array<string, array<string, ''>> $legacyImplementationMap
-   *   List of hooks and modules formatted for hook_module_implements_alter().
    * @param array<string, list<string>> $orderExtraTypes
    *   Extra types to order a hook with.
    */
-  protected static function registerImplementations(ContainerBuilder $container, HookCollectorPass $collector, array $implementations, array $legacyImplementationMap, array $orderExtraTypes): void {
+  protected static function registerImplementations(ContainerBuilder $container, HookCollectorPass $collector, array $implementations, array $orderExtraTypes): void {
     $container->register(ProceduralCall::class, ProceduralCall::class)
       ->addArgument($collector->includes);
 
@@ -208,7 +198,14 @@ protected static function registerImplementations(ContainerBuilder $container, H
       }
     }
 
-    // Register all implementations.
+    // List of hooks and modules formatted for hook_module_implements_alter().
+    $legacyImplementationMap = [];
+    foreach ($implementations as $hook => $implementationsByModule) {
+      foreach ($implementationsByModule as $module => $implementationsByClass) {
+        $legacyImplementationMap[$hook][$module] = '';
+      }
+    }
+
     foreach ($legacyImplementationMap as $hook => $moduleImplements) {
       $extraHooks = $orderExtraTypes[$hook] ?? [];
       // Add implementations to the array we pass to legacy ordering
-- 
GitLab


From d4778028043f804f07e7a5bbc5fef1bfb8d0fd99 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Sun, 2 Mar 2025 12:42:34 +0100
Subject: [PATCH 147/173] Rename some variables in foreach().

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 451323b98830..6cbe92256089 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -97,9 +97,9 @@ public function process(ContainerBuilder $container): array {
       ReOrderHook::class => [],
     ];
     foreach (array_keys($container->getParameter('container.modules')) as $module) {
-      foreach ($collector->moduleHooks[$module] ?? [] as $class => $methods) {
-        foreach ($methods as $method => $hooks) {
-          foreach ($hooks as $hookAttribute) {
+      foreach ($collector->moduleHooks[$module] ?? [] as $class => $attributesByMethod) {
+        foreach ($attributesByMethod as $method => $attributes) {
+          foreach ($attributes as $hookAttribute) {
             assert($hookAttribute instanceof HookOperation);
             if (isset($processAfter[get_class($hookAttribute)])) {
               $processAfter[get_class($hookAttribute)][] = $hookAttribute;
-- 
GitLab


From ad55bf715bd4539fd79ef170ae25c13c3423002f Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Mon, 3 Mar 2025 00:40:38 +0100
Subject: [PATCH 148/173] Change how empty arrays are removed.

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 42 ++++++-------------
 1 file changed, 13 insertions(+), 29 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 6cbe92256089..03c470b53de3 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -136,9 +136,21 @@ public function process(ContainerBuilder $container): array {
         // Remove the hook implementation for the defined class, method, and
         // hook.
         unset($implementations[$removeHook->hook][$module][$removeHook->class][$removeHook->method]);
+        // Remove empty arrays, after the entry was removed.
+        // Hook implementation removal is expected to be rare, therefore it will
+        // be faster to do it like this than cleaning the entire tree
+        // afterwards.
+        if (empty($implementations[$removeHook->hook][$module][$removeHook->class])) {
+          unset($implementations[$removeHook->hook][$module][$removeHook->class]);
+          if (empty($implementations[$removeHook->hook][$module])) {
+            unset($implementations[$removeHook->hook][$module]);
+            if (empty($implementations[$removeHook->hook])) {
+              unset($implementations[$removeHook->hook]);
+            }
+          }
+        }
       }
     }
-    $implementations = static::removeEmptyArraysRecursively($implementations);
 
     // Loop over all ReOrderHook attributes and gather order information
     // before registering the hooks. This must happen after all collection,
@@ -604,32 +616,4 @@ protected static function registerComplexHookImplementations(ContainerBuilder $c
     $container->setParameter('hook_implementations_map', $map);
   }
 
-  /**
-   * Removes empty sub-arrays recursively, preserving keys.
-   *
-   * This is different from NestedArray::filter() or regular array_filter():
-   *   - It only removes empty arrays, not other empty-ish values.
-   *   - It reliably removes empty parent arrays, after all children have been
-   *     removed. See https://www.drupal.org/project/drupal/issues/3381640.
-   *
-   * @param array $array
-   *   Array which may or may not contain other arrays as values.
-   *
-   * @return array
-   *   Filtered array, with empty arrays removed.
-   *
-   * @see \Drupal\Component\Utility\NestedArray::filter()
-   */
-  protected static function removeEmptyArraysRecursively(array $array): array {
-    foreach ($array as $key => $value) {
-      if (is_array($value)) {
-        $value = self::removeEmptyArraysRecursively($value);
-        if ($value === []) {
-          unset($array[$key]);
-        }
-      }
-    }
-    return $array;
-  }
-
 }
-- 
GitLab


From 6d42f0ac352e8eacb75522c07f9ff70dee523096 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Mon, 3 Mar 2025 00:42:28 +0100
Subject: [PATCH 149/173] Rename $legacyImplementationMap ->
 $moduleImplementsMap.

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 03c470b53de3..4078619c3aea 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -211,19 +211,19 @@ protected static function registerImplementations(ContainerBuilder $container, H
     }
 
     // List of hooks and modules formatted for hook_module_implements_alter().
-    $legacyImplementationMap = [];
+    $moduleImplementsMap = [];
     foreach ($implementations as $hook => $implementationsByModule) {
       foreach ($implementationsByModule as $module => $implementationsByClass) {
-        $legacyImplementationMap[$hook][$module] = '';
+        $moduleImplementsMap[$hook][$module] = '';
       }
     }
 
-    foreach ($legacyImplementationMap as $hook => $moduleImplements) {
+    foreach ($moduleImplementsMap as $hook => $moduleImplements) {
       $extraHooks = $orderExtraTypes[$hook] ?? [];
       // Add implementations to the array we pass to legacy ordering
       // when the definition specifies that they should be ordered together.
       foreach ($extraHooks as $extraHook) {
-        $moduleImplements += $legacyImplementationMap[$extraHook] ?? [];
+        $moduleImplements += $moduleImplementsMap[$extraHook] ?? [];
       }
       // Process all hook_module_implements_alter() for build time ordering.
       foreach ($collector->moduleImplementsAlters as $alter) {
-- 
GitLab


From 281661f3a78a9ba6b3cd55826b61a3602253e069 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Mon, 3 Mar 2025 00:44:59 +0100
Subject: [PATCH 150/173] Rename $method_hooks -> $methods.

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 4078619c3aea..c0595249580c 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -234,7 +234,7 @@ protected static function registerImplementations(ContainerBuilder $container, H
       // first.
       $priority = 0;
       foreach ($moduleImplements as $module => $v) {
-        foreach ($implementations[$hook][$module] ?? [] as $class => $method_hooks) {
+        foreach ($implementations[$hook][$module] ?? [] as $class => $methods) {
           if ($container->hasDefinition($class)) {
             $definition = $container->findDefinition($class);
           }
@@ -243,7 +243,7 @@ protected static function registerImplementations(ContainerBuilder $container, H
               ->register($class, $class)
               ->setAutowired(TRUE);
           }
-          foreach ($method_hooks as $method) {
+          foreach ($methods as $method) {
             $map[$hook][$class][$method] = $module;
             $priority = self::addTagToDefinition($definition, $hook, $method, $priority);
           }
-- 
GitLab


From 4e5e48e5125cbf6887fe6346b72f5de02a49c9c0 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Mon, 3 Mar 2025 01:08:54 +0100
Subject: [PATCH 151/173] Calculate $tagsInfoByClass before writing to the
 container.

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 30 +++++++++++++------
 1 file changed, 21 insertions(+), 9 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index c0595249580c..a1c63688cbc9 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -218,6 +218,7 @@ protected static function registerImplementations(ContainerBuilder $container, H
       }
     }
 
+    $tagsInfoByClass = [];
     foreach ($moduleImplementsMap as $hook => $moduleImplements) {
       $extraHooks = $orderExtraTypes[$hook] ?? [];
       // Add implementations to the array we pass to legacy ordering
@@ -235,23 +236,34 @@ protected static function registerImplementations(ContainerBuilder $container, H
       $priority = 0;
       foreach ($moduleImplements as $module => $v) {
         foreach ($implementations[$hook][$module] ?? [] as $class => $methods) {
-          if ($container->hasDefinition($class)) {
-            $definition = $container->findDefinition($class);
-          }
-          else {
-            $definition = $container
-              ->register($class, $class)
-              ->setAutowired(TRUE);
-          }
           foreach ($methods as $method) {
+            $tagsInfoByClass[$class][] = [
+              'event' => "drupal_hook.$hook",
+              'method' => $method,
+              'priority' => $priority,
+            ];
+            --$priority;
             $map[$hook][$class][$method] = $module;
-            $priority = self::addTagToDefinition($definition, $hook, $method, $priority);
           }
         }
         unset($implementations[$hook][$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);
+      }
+    }
+
     // Pass necessary parameters to moduleHandler.
     $definition = $container->getDefinition('module_handler');
     $definition->setArgument('$groupIncludes', $groupIncludes);
-- 
GitLab


From a17d9b7d5eedcbd4b7b86dbd70f95b4daae0dbb6 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Mon, 3 Mar 2025 02:09:49 +0100
Subject: [PATCH 152/173] Add line breaks, convert arrow function to regular
 closure.

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index a1c63688cbc9..37cd038f2ac4 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -302,7 +302,12 @@ protected static function reOrderImplementations(ContainerBuilder $container, ar
         // $hookOrderOperation->order->classesAndMethods and create specifiers
         // for HookPriority::change() while at it.
         $otherSpecifiers = array_map(
-          static fn ($pair) => is_array($pair) ? $pair[0] . '::' . $pair[1] : throw new \LogicException('classesAndMethods needs to be an array of arrays'),
+          static function ($pair) {
+            if (!is_array($pair)) {
+              return throw new \LogicException('classesAndMethods needs to be an array of arrays');
+            }
+            return $pair[0] . '::' . $pair[1];
+          },
           $hookOrderOperation->order->classesAndMethods
         );
         // Collect classes and methods for
-- 
GitLab


From 181b3358387bff04cd6ac1cf7e148c14e722e936 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Tue, 4 Mar 2025 12:34:38 +0100
Subject: [PATCH 153/173] Collapse reOrderImplementations() into
 registerImplementations().

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 40 ++++++++-----------
 1 file changed, 17 insertions(+), 23 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 37cd038f2ac4..86a005467fe1 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -174,8 +174,7 @@ public function process(ContainerBuilder $container): array {
     // is removed.
     // @see https://www.drupal.org/project/drupal/issues/3481778
     if (count($container->getDefinitions()) > 1) {
-      static::registerImplementations($container, $collector, $implementations, $orderExtraTypes);
-      static::reOrderImplementations($container, $hookOrderOperations, $orderExtraTypes, $implementations, $moduleFinder);
+      static::registerImplementations($container, $collector, $implementations, $orderExtraTypes, $hookOrderOperations, $moduleFinder);
     }
     return $implementations;
   }
@@ -193,8 +192,23 @@ public function process(ContainerBuilder $container): array {
    *   All implementations, as method names keyed by hook, module and class.
    * @param array<string, list<string>> $orderExtraTypes
    *   Extra types to order a hook with.
+   * @param list<\Drupal\Core\Hook\HookOperation> $hookOrderOperations
+   *   All attributes that contain ordering information.
+   * @param array<class-string, array<string, array<string, string>>> $moduleFinder
+   *   Lookup map to find the module for each hook implementation.
+   *   Array keys are the class, method, and hook, array values are module
+   *   names.
+   *   The module name can be different from the module the class is in,
+   *   because an implementation can be on behalf of another module.
    */
-  protected static function registerImplementations(ContainerBuilder $container, HookCollectorPass $collector, array $implementations, array $orderExtraTypes): void {
+  protected static function registerImplementations(
+    ContainerBuilder $container,
+    HookCollectorPass $collector,
+    array $implementations,
+    array $orderExtraTypes,
+    array $hookOrderOperations,
+    array $moduleFinder,
+  ): void {
     $container->register(ProceduralCall::class, ProceduralCall::class)
       ->addArgument($collector->includes);
 
@@ -269,27 +283,7 @@ protected static function registerImplementations(ContainerBuilder $container, H
     $definition->setArgument('$groupIncludes', $groupIncludes);
     $definition->setArgument('$orderedExtraTypes', $orderExtraTypes);
     $container->setParameter('hook_implementations_map', $map ?? []);
-  }
 
-  /**
-   * Reorder hook implementations specifying an order.
-   *
-   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
-   *   The container.
-   * @param list<\Drupal\Core\Hook\HookOperation> $hookOrderOperations
-   *   All attributes that contain ordering information.
-   * @param array<string, list<string>> $orderExtraTypes
-   *   Lists of extra hooks to order together with, keyed by hook name.
-   * @param array<string, array<string, array<class-string, list<string>>>> $implementations
-   *   Hook implementations, as method names by hook, module and class.
-   * @param array<class-string, array<string, array<string, string>>> $moduleFinder
-   *   Lookup map to find the module for each hook implementation.
-   *   Array keys are the class, method, and hook, array values are module
-   *   names.
-   *   The module name can be different from the module the class is in,
-   *   because an implementation can be on behalf of another module.
-   */
-  protected static function reOrderImplementations(ContainerBuilder $container, array $hookOrderOperations, array $orderExtraTypes, array $implementations, array $moduleFinder): void {
     $hookPriority = new HookPriority($container);
     foreach ($hookOrderOperations as $hookOrderOperation) {
       assert($hookOrderOperation instanceof HookOperation);
-- 
GitLab


From 1812244466d356cc346c22d953b0242c66efb1f2 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Tue, 4 Mar 2025 14:13:40 +0100
Subject: [PATCH 154/173] Calculate $otherSpecifiers later.

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 25 +++++++++----------
 1 file changed, 12 insertions(+), 13 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 86a005467fe1..1c1925c0c886 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -292,18 +292,6 @@ protected static function registerImplementations(
       $hooks = $orderExtraTypes[$hookOrderOperation->hook] ?? [$hookOrderOperation->hook];
       $combinedHook = implode(':', $hooks);
       if ($hookOrderOperation->order instanceof ComplexOrder) {
-        // Verify the correct structure of
-        // $hookOrderOperation->order->classesAndMethods and create specifiers
-        // for HookPriority::change() while at it.
-        $otherSpecifiers = array_map(
-          static function ($pair) {
-            if (!is_array($pair)) {
-              return throw new \LogicException('classesAndMethods needs to be an array of arrays');
-            }
-            return $pair[0] . '::' . $pair[1];
-          },
-          $hookOrderOperation->order->classesAndMethods
-        );
         // Collect classes and methods for
         // self::registerComplexHookImplementations().
         $classesAndMethods = $hookOrderOperation->order->classesAndMethods;
@@ -312,11 +300,22 @@ static function ($pair) {
             foreach ($implementations[$hook][$module] ?? [] as $class => $methods) {
               foreach ($methods as $method) {
                 $classesAndMethods[] = [$class, $method];
-                $otherSpecifiers[] = "$class::$method";
               }
             }
           }
         }
+        // Verify the correct structure of
+        // $hookOrderOperation->order->classesAndMethods and create specifiers
+        // for HookPriority::change() while at it.
+        $otherSpecifiers = array_map(
+          static function ($pair) {
+            if (!is_array($pair)) {
+              return throw new \LogicException('classesAndMethods needs to be an array of arrays');
+            }
+            return $pair[0] . '::' . $pair[1];
+          },
+          $classesAndMethods,
+        );
         if (count($hooks) > 1) {
           // The hook implementation in $hookOrderOperation and everything in
           // $classesAndMethods will be ordered relative to each other as if
-- 
GitLab


From 67858150287ad9d6caec39ff0eda7d44256bd69a Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Tue, 4 Mar 2025 14:24:50 +0100
Subject: [PATCH 155/173] Collapse HookPriority::change() into
 HookCollectorPass::registerImplementations().

---
 .../Drupal/Core/Hook/HookCollectorPass.php    |  64 ++++++++-
 core/lib/Drupal/Core/Hook/HookPriority.php    |  80 -----------
 .../Hook/HookPriorityEqualPriorityTest.php    |  64 ---------
 .../Tests/Core/Hook/HookPriorityTest.php      | 124 ------------------
 .../Tests/Core/Hook/HookPriorityTestBase.php  |  85 ------------
 5 files changed, 63 insertions(+), 354 deletions(-)
 delete mode 100644 core/tests/Drupal/Tests/Core/Hook/HookPriorityEqualPriorityTest.php
 delete mode 100644 core/tests/Drupal/Tests/Core/Hook/HookPriorityTest.php
 delete mode 100644 core/tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 1c1925c0c886..7fd94d6a8a61 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -329,7 +329,69 @@ static function ($pair) {
       else {
         $otherSpecifiers = NULL;
       }
-      $hookPriority->change("drupal_hook.$combinedHook", $hookOrderOperation, $otherSpecifiers);
+
+      foreach ($container->findTaggedServiceIds('kernel.event_listener') as $id => $tags) {
+        foreach ($tags as $key => $tag) {
+          if ($tag['event'] === "drupal_hook.$combinedHook") {
+            $index = "$id.$key";
+            $priority = $tag['priority'];
+            // Symfony documents event listener priorities to be integers,
+            // HookCollectorPass sets them to be integers, ::set() only
+            // accepts integers.
+            assert(is_int($priority));
+            $priorities[$index] = $priority;
+            $specifier = "$id::" . $tag['method'];
+            if ($specifier === "$hookOrderOperation->class::$hookOrderOperation->method") {
+              $index_this = $index;
+            }
+            // $other_specifiers is defined for before and after, for these
+            // compare only the priority of those. For first and last the
+            // priority of every other hook matters.
+            elseif (!isset($otherSpecifiers) || in_array($specifier, $otherSpecifiers)) {
+              $priorities_other[$specifier] = $priority;
+            }
+          }
+        }
+      }
+      if (!isset($index_this) || !isset($priorities) || !isset($priorities_other)) {
+        return;
+      }
+
+      $shouldBeLarger = (bool) $hookOrderOperation->order->value;
+      // The priority of the hook being changed.
+      $priority_this = $priorities[$index_this];
+      // The priority of the hook being compared to.
+      $priority_other = $shouldBeLarger ? max($priorities_other) : min($priorities_other);
+      // If the order is correct there is nothing to do. If the two priorities
+      // are the same then the order is undefined and so it can't be correct.
+      // If they are not the same and $priority_this is already larger exactly
+      // when $shouldBeLarger says then it's the correct order.
+      if ($priority_this !== $priority_other && ($shouldBeLarger === ($priority_this > $priority_other))) {
+        return;
+      }
+      $priority_new = $priority_other + ($shouldBeLarger ? 1 : -1);
+      // For first and last this new priority is already larger/smaller
+      // than all existing priorities but for before / after it might belong to
+      // an already existing hook. In this case set the new priority temporarily
+      // to be halfway between $priority_other and $priority_new then give all
+      // hook implementations new, integer priorities keeping this new order.
+      // This ensures the hook implementation being changed is in the right order
+      // relative to both $priority_other and the hook whose priority was
+      // $priority_new.
+      if (in_array($priority_new, $priorities)) {
+        $priorities[$index_this] = $priority_other + ($shouldBeLarger ? 0.5 : -0.5);
+        asort($priorities);
+        $changed_indexes = array_keys($priorities);
+        $priorities = array_combine($changed_indexes, range(1, count($changed_indexes)));
+      }
+      else {
+        $priorities[$index_this] = $priority_new;
+        $changed_indexes = [$index_this];
+      }
+      foreach ($changed_indexes as $index) {
+        [$id, $key] = explode('.', $index);
+        $hookPriority->set($id, (int) $key, $priorities[$index]);
+      }
     }
   }
 
diff --git a/core/lib/Drupal/Core/Hook/HookPriority.php b/core/lib/Drupal/Core/Hook/HookPriority.php
index 36f6a076b6ef..fa271cd07df3 100644
--- a/core/lib/Drupal/Core/Hook/HookPriority.php
+++ b/core/lib/Drupal/Core/Hook/HookPriority.php
@@ -15,86 +15,6 @@ class HookPriority {
 
   public function __construct(protected ContainerBuilder $container) {}
 
-  /**
-   * Change the priority of a hook implementation.
-   *
-   * @param string $event
-   *   Listeners to this event will be ordered.
-   * @param \Drupal\Core\Hook\HookOperation $hook
-   *   The hook attribute. Most of the order parameter is ignored by this
-   *   class, only $hook->order->value is used. The rest is preprocessed by
-   *   HookCollectorPass and passed in $other_specifiers.
-   * @param array|null $other_specifiers
-   *   Other hook implementations to compare to, if any. The array is a list of
-   *   strings, each string is a class and method separated by ::.
-   *
-   * @internal
-   */
-  public function change(string $event, HookOperation $hook, ?array $other_specifiers = NULL): void {
-    foreach ($this->container->findTaggedServiceIds('kernel.event_listener') as $id => $tags) {
-      foreach ($tags as $key => $tag) {
-        if ($tag['event'] === $event) {
-          $index = "$id.$key";
-          $priority = $tag['priority'];
-          // Symfony documents event listener priorities to be integers,
-          // HookCollectorPass sets them to be integers, ::set() only
-          // accepts integers.
-          assert(is_int($priority));
-          $priorities[$index] = $priority;
-          $specifier = "$id::" . $tag['method'];
-          if ($specifier === "$hook->class::$hook->method") {
-            $index_this = $index;
-          }
-          // $other_specifiers is defined for before and after, for these
-          // compare only the priority of those. For first and last the
-          // priority of every other hook matters.
-          elseif (!isset($other_specifiers) || in_array($specifier, $other_specifiers)) {
-            $priorities_other[$specifier] = $priority;
-          }
-        }
-      }
-    }
-    if (!isset($index_this) || !isset($priorities) || !isset($priorities_other)) {
-      return;
-    }
-
-    $shouldBeLarger = (bool) $hook->order->value;
-    // The priority of the hook being changed.
-    $priority_this = $priorities[$index_this];
-    // The priority of the hook being compared to.
-    $priority_other = $shouldBeLarger ? max($priorities_other) : min($priorities_other);
-    // If the order is correct there is nothing to do. If the two priorities
-    // are the same then the order is undefined and so it can't be correct.
-    // If they are not the same and $priority_this is already larger exactly
-    // when $shouldBeLarger says then it's the correct order.
-    if ($priority_this !== $priority_other && ($shouldBeLarger === ($priority_this > $priority_other))) {
-      return;
-    }
-    $priority_new = $priority_other + ($shouldBeLarger ? 1 : -1);
-    // For first and last this new priority is already larger/smaller
-    // than all existing priorities but for before / after it might belong to
-    // an already existing hook. In this case set the new priority temporarily
-    // to be halfway between $priority_other and $priority_new then give all
-    // hook implementations new, integer priorities keeping this new order.
-    // This ensures the hook implementation being changed is in the right order
-    // relative to both $priority_other and the hook whose priority was
-    // $priority_new.
-    if (in_array($priority_new, $priorities)) {
-      $priorities[$index_this] = $priority_other + ($shouldBeLarger ? 0.5 : -0.5);
-      asort($priorities);
-      $changed_indexes = array_keys($priorities);
-      $priorities = array_combine($changed_indexes, range(1, count($changed_indexes)));
-    }
-    else {
-      $priorities[$index_this] = $priority_new;
-      $changed_indexes = [$index_this];
-    }
-    foreach ($changed_indexes as $index) {
-      [$id, $key] = explode('.', $index);
-      $this->set($id, (int) $key, $priorities[$index]);
-    }
-  }
-
   /**
    * Set the priority of a listener.
    *
diff --git a/core/tests/Drupal/Tests/Core/Hook/HookPriorityEqualPriorityTest.php b/core/tests/Drupal/Tests/Core/Hook/HookPriorityEqualPriorityTest.php
deleted file mode 100644
index 985e3d55c997..000000000000
--- a/core/tests/Drupal/Tests/Core/Hook/HookPriorityEqualPriorityTest.php
+++ /dev/null
@@ -1,64 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Tests\Core\Hook;
-
-use Drupal\Core\Hook\Order;
-use Drupal\Core\Hook\OrderAfter;
-use Drupal\Core\Hook\OrderBefore;
-
-/**
- * @coversDefaultClass \Drupal\Core\Hook\HookPriority
- *
- * @group Hook
- */
-class HookPriorityEqualPriorityTest extends HookPriorityTestBase {
-
-  protected function setUp(): void {
-    parent::setUp();
-    // The priority of "a", "b", "c" are the same, the order is undefined.
-    $this->setUpContainer(FALSE);
-    $this->assertSame($this->getPriority('a'), $this->getPriority('b'));
-    $this->assertSame($this->getPriority('b'), $this->getPriority('c'));
-  }
-
-  public function testFirst(): void {
-    // "c" was first, make "a" the first.
-    $this->doPriorityChange('a', Order::First);
-    $this->assertGreaterThan($this->getPriority('c'), $this->getPriority('a'));
-    $this->assertGreaterThan($this->getPriority('b'), $this->getPriority('a'));
-    // Nothing else can be asserted: by setting the same priority, the setup
-    // had undefined order and so the services not included in the helper call
-    // can be in any order.
-  }
-
-  public function testLast(): void {
-    // "c" was first, make it the last.
-    $this->doPriorityChange('c', Order::Last);
-    $this->assertGreaterThan($this->getPriority('c'), $this->getPriority('a'));
-    $this->assertGreaterThan($this->getPriority('c'), $this->getPriority('b'));
-    // Nothing else can be asserted: by setting the same priority, the setup
-    // had undefined order and so the services not included in the helper call
-    // can be in any order.
-  }
-
-  public function testBefore(): void {
-    // "a" was last, move it before "b".
-    $this->doPriorityChange('a', OrderBefore::class, 'b');
-    $this->assertGreaterThan($this->getPriority('b'), $this->getPriority('a'));
-    // Nothing else can be asserted: by setting the same priority, the setup
-    // had undefined order and so the services not included in the helper call
-    // can be in any order.
-  }
-
-  public function testAfter(): void {
-    // "c" was first, move it after "b".
-    $this->doPriorityChange('c', OrderAfter::class, 'b');
-    $this->assertGreaterThan($this->getPriority('c'), $this->getPriority('b'));
-    // Nothing else can be asserted: by setting the same priority, the setup
-    // had undefined order and so the services not included in the helper call
-    // can be in any order.
-  }
-
-}
diff --git a/core/tests/Drupal/Tests/Core/Hook/HookPriorityTest.php b/core/tests/Drupal/Tests/Core/Hook/HookPriorityTest.php
deleted file mode 100644
index 65c8193b8f83..000000000000
--- a/core/tests/Drupal/Tests/Core/Hook/HookPriorityTest.php
+++ /dev/null
@@ -1,124 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Tests\Core\Hook;
-
-use Drupal\Core\Hook\Order;
-use Drupal\Core\Hook\OrderAfter;
-use Drupal\Core\Hook\OrderBefore;
-
-/**
- * @coversDefaultClass \Drupal\Core\Hook\HookPriority
- *
- * @group Hook
- */
-class HookPriorityTest extends HookPriorityTestBase {
-
-  /**
-   * The original priorities.
-   *
-   * @var array
-   */
-  protected array $originalPriorities = [];
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp(): void {
-    parent::setUp();
-    // "c" first, "b" second, "a" last.
-    $this->setUpContainer(TRUE);
-    foreach (['a', 'b', 'c'] as $key) {
-      $this->originalPriorities[$key] = $this->getPriority($key);
-    }
-    // The documentation does not clarify the order of arguments, let's do so
-    // here to make it easier to develop/debug this test.
-    $this->assertGreaterThan(1, 2);
-    // According to https://symfony.com/doc/current/event_dispatcher.html
-    // the higher the number, the earlier a listener is executed. Accordingly
-    // assert that "a" is last, "c" is first, "b" is in the middle. The
-    // asserts in methods can be compared to these establishing asserts.
-    $this->assertGreaterThan($this->getPriority('a'), $this->getPriority('b'));
-    $this->assertGreaterThan($this->getPriority('b'), $this->getPriority('c'));
-    // This is unnecessary, but it's easier to compare if this is explicit.
-    $this->assertGreaterThan($this->getPriority('a'), $this->getPriority('c'));
-  }
-
-  public function testFirst(): void {
-    // "c" was first, make "a" the first.
-    $this->doPriorityChange('a', Order::First);
-    $this->assertGreaterThan($this->getPriority('c'), $this->getPriority('a'));
-    $this->assertGreaterThan($this->getPriority('b'), $this->getPriority('a'));
-    // The other two shouldn't change.
-    $this->assertNoChange('a');
-  }
-
-  public function testLast(): void {
-    // "c" was first, make it the last.
-    $this->doPriorityChange('c', Order::Last);
-    $this->assertGreaterThan($this->getPriority('c'), $this->getPriority('a'));
-    $this->assertGreaterThan($this->getPriority('c'), $this->getPriority('b'));
-    // The other two shouldn't change.
-    $this->assertNoChange('c');
-  }
-
-  public function testBefore(): void {
-    // "a" was last, move it before "b".
-    $this->doPriorityChange('a', OrderBefore::class, 'b');
-    $this->assertGreaterThan($this->getPriority('b'), $this->getPriority('a'));
-    // The relation between these should not change. The actual priority
-    // might.
-    $this->assertGreaterThan($this->getPriority('b'), $this->getPriority('c'));
-    $this->assertGreaterThan($this->getPriority('a'), $this->getPriority('c'));
-  }
-
-  public function testAfter(): void {
-    // "c" was first, move it after "b".
-    $this->doPriorityChange('c', OrderAfter::class, 'b');
-    $this->assertGreaterThan($this->getPriority('c'), $this->getPriority('b'));
-    // The relation between these should not change. The actual priority
-    // might.
-    $this->assertGreaterThan($this->getPriority('a'), $this->getPriority('b'));
-    $this->assertGreaterThan($this->getPriority('a'), $this->getPriority('c'));
-  }
-
-  public function testFirstNoChange(): void {
-    // "c" was first, making it first should be a no-op.
-    $this->doPriorityChange('c', Order::First);
-    $this->assertNoChange();
-  }
-
-  public function testLastNoChange(): void {
-    // "a" was last, making it last should be a no-op.
-    $this->doPriorityChange('a', Order::Last);
-    $this->assertNoChange();
-  }
-
-  public function testBeforeNoChange(): void {
-    // "b" is already firing before "a", this should be a no-op.
-    $this->doPriorityChange('b', OrderBefore::class, 'a');
-    $this->assertNoChange();
-  }
-
-  public function testAfterNoChange(): void {
-    // "b' is already firing after "c", this should be a no-op.
-    $this->doPriorityChange('b', OrderAfter::class, 'c');
-    $this->assertNoChange();
-  }
-
-  /**
-   * Asserts no change has happened.
-   *
-   * @param string $changed
-   *   This one did change. Assert the rest did not change.
-   */
-  protected function assertNoChange(string $changed = ''): void {
-    foreach ($this->originalPriorities as $key => $priority) {
-      if ($key !== $changed) {
-        $this->assertSame($priority, $this->getPriority($key));
-      }
-    }
-  }
-
-}
diff --git a/core/tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php b/core/tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php
deleted file mode 100644
index 36cbd21055f0..000000000000
--- a/core/tests/Drupal/Tests/Core/Hook/HookPriorityTestBase.php
+++ /dev/null
@@ -1,85 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Tests\Core\Hook;
-
-use Drupal\Core\DependencyInjection\ContainerBuilder;
-use Drupal\Core\Hook\Attribute\Hook;
-use Drupal\Core\Hook\HookPriority;
-use Drupal\Core\Hook\Order;
-use Drupal\Tests\UnitTestCase;
-
-/**
- * Base class for testing HookPriority.
- */
-abstract class HookPriorityTestBase extends UnitTestCase {
-
-  /**
-   * The container builder.
-   *
-   * @var \Drupal\Core\DependencyInjection\ContainerBuilder
-   */
-  protected ContainerBuilder $container;
-
-  /**
-   * Set up three service listeners, "a", "b" and "c".
-   *
-   * The service id, the class name and the method name are all the same.
-   *
-   * @param bool $different_priority
-   *   When TRUE, "c" will fire first, "b" second and "a" last. When FALSE,
-   *   the priority will be set to be the same and the order is undefined.
-   */
-  protected function setUpContainer(bool $different_priority): void {
-    $this->container = new ContainerBuilder();
-    foreach (['a', 'b', 'c'] as $key => $name) {
-      $definition = $this->container
-        ->register($name, $name)
-        ->setAutowired(TRUE);
-      $definition->addTag('kernel.event_listener', [
-        'event' => 'drupal_hook.test',
-        'method' => $name,
-        // Do not use $key itself to avoid a 0 priority which could potentially
-        // lead to misleading results.
-        'priority' => $different_priority ? $key + 3 : 0,
-      ]);
-    }
-  }
-
-  /**
-   * Get the priority for a service.
-   */
-  protected function getPriority(string $name): int {
-    $definition = $this->container->getDefinition($name);
-    return $definition->getTags()['kernel.event_listener'][0]['priority'];
-  }
-
-  /**
-   * Change priority of a class and method.
-   *
-   * @param class-string $classBeingChanged
-   *   The class being changed, the method has the same name.
-   * @param \Drupal\Core\Hook\Order|class-string $order
-   *   Either a member of the Order enum or the name of a ComplexOrder class.
-   * @param class-string $relativeTo
-   *   If the operation is before or after, this is the name of the class
-   *   the operation changes relative to.
-   */
-  protected function doPriorityChange(string $classBeingChanged, Order|string $order, string $relativeTo = ''): void {
-    if ($relativeTo) {
-      // The modules / classesAndMethods argument of the order class is
-      // processed in HookCollectorPass and is ignored by HookPriority, they
-      // are passed to HookPriority in the $other_specifiers argument.
-      $hook = new Hook('test', order: new $order(modules: ['']));
-      $other_specifiers = ["$relativeTo::$relativeTo"];
-    }
-    else {
-      $hook = new Hook('test', order: $order);
-      $other_specifiers = NULL;
-    }
-    $hook->set(class: $classBeingChanged, module: '', method: $classBeingChanged);
-    (new HookPriority($this->container))->change('drupal_hook.test', $hook, $other_specifiers);
-  }
-
-}
-- 
GitLab


From eb59286b1d0030f7d150e0f00111f7dc4556c3b2 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Tue, 4 Mar 2025 14:30:42 +0100
Subject: [PATCH 156/173] Collapse HookPriority::set() into
 HookCollectorPass::registerImplementations().

---
 .../Drupal/Core/Hook/HookCollectorPass.php    |  8 +++--
 core/lib/Drupal/Core/Hook/HookPriority.php    | 36 -------------------
 2 files changed, 5 insertions(+), 39 deletions(-)
 delete mode 100644 core/lib/Drupal/Core/Hook/HookPriority.php

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 7fd94d6a8a61..727469820c4a 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -284,7 +284,6 @@ protected static function registerImplementations(
     $definition->setArgument('$orderedExtraTypes', $orderExtraTypes);
     $container->setParameter('hook_implementations_map', $map ?? []);
 
-    $hookPriority = new HookPriority($container);
     foreach ($hookOrderOperations as $hookOrderOperation) {
       assert($hookOrderOperation instanceof HookOperation);
       // ::process() adds the hook serving as key to the order extraTypes so it
@@ -389,8 +388,11 @@ static function ($pair) {
         $changed_indexes = [$index_this];
       }
       foreach ($changed_indexes as $index) {
-        [$id, $key] = explode('.', $index);
-        $hookPriority->set($id, (int) $key, $priorities[$index]);
+        [$id1, $key1] = explode('.', $index);
+        $definition = $container->findDefinition($id1);
+        $tags = $definition->getTags();
+        $tags['kernel.event_listener'][$key1]['priority'] = $priorities[$index];
+        $definition->setTags($tags);
       }
     }
   }
diff --git a/core/lib/Drupal/Core/Hook/HookPriority.php b/core/lib/Drupal/Core/Hook/HookPriority.php
deleted file mode 100644
index fa271cd07df3..000000000000
--- a/core/lib/Drupal/Core/Hook/HookPriority.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\Core\Hook;
-
-use Symfony\Component\DependencyInjection\ContainerBuilder;
-
-/**
- * Helper class for HookCollectorPass to change the priority of listeners.
- *
- * @internal
- */
-class HookPriority {
-
-  public function __construct(protected ContainerBuilder $container) {}
-
-  /**
-   * Set the priority of a listener.
-   *
-   * @param string $class
-   *   The name of the class, this is the same as the service id.
-   * @param int $key
-   *   The key within the tags array of the 'kernel.event_listener' tag for the
-   *   hook implementation to be changed.
-   * @param int $priority
-   *   The new priority.
-   */
-  public function set(string $class, int $key, int $priority): void {
-    $definition = $this->container->findDefinition($class);
-    $tags = $definition->getTags();
-    $tags['kernel.event_listener'][$key]['priority'] = $priority;
-    $definition->setTags($tags);
-  }
-
-}
-- 
GitLab


From 1f0bbe58d659d3bde23933c168288b16c063b98a Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Tue, 4 Mar 2025 15:57:00 +0100
Subject: [PATCH 157/173] Do not unset the entry in $implementations, because
 no reason to.

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 1 -
 1 file changed, 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 727469820c4a..6e45be932c3b 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -260,7 +260,6 @@ protected static function registerImplementations(
             $map[$hook][$class][$method] = $module;
           }
         }
-        unset($implementations[$hook][$module]);
       }
     }
 
-- 
GitLab


From fa4085584817d2d5f60a6c5e8026139542d0670a Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Tue, 4 Mar 2025 15:58:56 +0100
Subject: [PATCH 158/173] Initialize $map before foreach().

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 6e45be932c3b..3e4924d99426 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -233,6 +233,7 @@ protected static function registerImplementations(
     }
 
     $tagsInfoByClass = [];
+    $map = [];
     foreach ($moduleImplementsMap as $hook => $moduleImplements) {
       $extraHooks = $orderExtraTypes[$hook] ?? [];
       // Add implementations to the array we pass to legacy ordering
@@ -281,7 +282,7 @@ protected static function registerImplementations(
     $definition = $container->getDefinition('module_handler');
     $definition->setArgument('$groupIncludes', $groupIncludes);
     $definition->setArgument('$orderedExtraTypes', $orderExtraTypes);
-    $container->setParameter('hook_implementations_map', $map ?? []);
+    $container->setParameter('hook_implementations_map', $map);
 
     foreach ($hookOrderOperations as $hookOrderOperation) {
       assert($hookOrderOperation instanceof HookOperation);
-- 
GitLab


From eaa138c1983518887ca658320bbbba753713ea28 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Tue, 4 Mar 2025 16:00:43 +0100
Subject: [PATCH 159/173] Calculate $map and $tagsInfoByClass in separate
 foreach().

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 31 +++++++++++--------
 1 file changed, 18 insertions(+), 13 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 3e4924d99426..febafe7d6a7f 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -232,8 +232,7 @@ protected static function registerImplementations(
       }
     }
 
-    $tagsInfoByClass = [];
-    $map = [];
+    $alteredImplementations = [];
     foreach ($moduleImplementsMap as $hook => $moduleImplements) {
       $extraHooks = $orderExtraTypes[$hook] ?? [];
       // Add implementations to the array we pass to legacy ordering
@@ -245,25 +244,31 @@ protected static function registerImplementations(
       foreach ($collector->moduleImplementsAlters as $alter) {
         $alter($moduleImplements, $hook);
       }
-      // Start at 0 for the first hook. We decrease the priority after each
-      // hook that is registered. Symfony priorities run higher priorities
-      // first.
-      $priority = 0;
       foreach ($moduleImplements as $module => $v) {
         foreach ($implementations[$hook][$module] ?? [] as $class => $methods) {
           foreach ($methods as $method) {
-            $tagsInfoByClass[$class][] = [
-              'event' => "drupal_hook.$hook",
-              'method' => $method,
-              'priority' => $priority,
-            ];
-            --$priority;
-            $map[$hook][$class][$method] = $module;
+            $alteredImplementations[$hook]["$class::$method"] = $module;
           }
         }
       }
     }
 
+    $map = [];
+    $tagsInfoByClass = [];
+    foreach ($alteredImplementations 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);
-- 
GitLab


From 714bf2cfb03ff15db9d7c26b4172456012849576 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Tue, 4 Mar 2025 16:16:51 +0100
Subject: [PATCH 160/173] Collapse registerComplexHookImplementations() into
 registerImplementations().

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 44 ++++++-------------
 1 file changed, 14 insertions(+), 30 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index febafe7d6a7f..546f92ae8a95 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -327,7 +327,20 @@ static function ($pair) {
           // their service definition and added to the
           // hook_implementations_map container parameter.
           $classesAndMethods[] = [$hookOrderOperation->class, $hookOrderOperation->method];
-          self::registerComplexHookImplementations($container, $classesAndMethods, $moduleFinder, $combinedHook);
+
+          $map1 = $container->getParameter('hook_implementations_map');
+          $priority1 = 0;
+          foreach ($classesAndMethods as [$class1, $method1]) {
+            // Ordering against not installed modules is possible.
+            if (isset($moduleFinder[$class1][$method1])) {
+              if (count(array_unique($moduleFinder[$class1][$method1])) > 1) {
+                throw new \LogicException('Complex ordering can only work when all implementations on a single method are for the same module.');
+              }
+              $map1[$combinedHook][$class1][$method1] = reset($moduleFinder[$class1][$method1]);
+              $priority1 = self::addTagToDefinition($container->findDefinition($class1), $combinedHook, $method1, $priority1);
+            }
+          }
+          $container->setParameter('hook_implementations_map', $map1);
         }
       }
       else {
@@ -666,33 +679,4 @@ protected static function addTagToDefinition(Definition $definition, string|int
     return $priority;
   }
 
-  /**
-   * Register complex hook implementations.
-   *
-   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
-   *   The container.
-   * @param list<array{class-string, string}> $classesAndMethods
-   *   A list of class-and-method pairs.
-   * @param array<class-string, array<string, array<string, string>>> $moduleFinder
-   *   Array keys are the class, method, and hook, array values are module
-   *   names.
-   * @param string $combinedHook
-   *   A string made form list of hooks separated by :.
-   */
-  protected static function registerComplexHookImplementations(ContainerBuilder $container, array $classesAndMethods, array $moduleFinder, string $combinedHook): void {
-    $map = $container->getParameter('hook_implementations_map');
-    $priority = 0;
-    foreach ($classesAndMethods as [$class, $method]) {
-      // Ordering against not installed modules is possible.
-      if (isset($moduleFinder[$class][$method])) {
-        if (count(array_unique($moduleFinder[$class][$method])) > 1) {
-          throw new \LogicException('Complex ordering can only work when all implementations on a single method are for the same module.');
-        }
-        $map[$combinedHook][$class][$method] = reset($moduleFinder[$class][$method]);
-        $priority = self::addTagToDefinition($container->findDefinition($class), $combinedHook, $method, $priority);
-      }
-    }
-    $container->setParameter('hook_implementations_map', $map);
-  }
-
 }
-- 
GitLab


From a59d76463d3a0fa89ab4b0462648f63c84a52c33 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Thu, 6 Mar 2025 18:40:16 +0100
Subject: [PATCH 161/173] Collapse addTagToDefinition() into
 registerImplementations().

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 30 ++++---------------
 1 file changed, 5 insertions(+), 25 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 546f92ae8a95..8cc67e7e53ce 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -337,7 +337,11 @@ static function ($pair) {
                 throw new \LogicException('Complex ordering can only work when all implementations on a single method are for the same module.');
               }
               $map1[$combinedHook][$class1][$method1] = reset($moduleFinder[$class1][$method1]);
-              $priority1 = self::addTagToDefinition($container->findDefinition($class1), $combinedHook, $method1, $priority1);
+              $container->findDefinition($class1)->addTag('kernel.event_listener', [
+                'event' => "drupal_hook.$combinedHook",
+                'method' => $method1,
+                'priority' => $priority1--,
+              ]);
             }
           }
           $container->setParameter('hook_implementations_map', $map1);
@@ -655,28 +659,4 @@ protected static function getAttributeInstances(\ReflectionClass $reflectionClas
     return $attributes;
   }
 
-  /**
-   * Adds an event listener tag to a service definition.
-   *
-   * @param \Symfony\Component\DependencyInjection\Definition $definition
-   *   The service definition.
-   * @param string|int $hook
-   *   The name of the hook.
-   * @param string $method
-   *   The method.
-   * @param int $priority
-   *   The priority.
-   *
-   * @return int
-   *   A new priority, guaranteed to be lower than $priority.
-   */
-  protected static function addTagToDefinition(Definition $definition, string|int $hook, string $method, int $priority): int {
-    $definition->addTag('kernel.event_listener', [
-      'event' => "drupal_hook.$hook",
-      'method' => $method,
-      'priority' => $priority--,
-    ]);
-    return $priority;
-  }
-
 }
-- 
GitLab


From ca3680d260ef2d8c9c3a9bfb3929eca9f614dcd9 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Fri, 7 Mar 2025 01:19:41 +0100
Subject: [PATCH 162/173] Rewrite parts of HookCollectorPass to calculate
 first, then write to container.

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 275 +++++++++---------
 1 file changed, 138 insertions(+), 137 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 8cc67e7e53ce..96806ded7509 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -16,7 +16,6 @@
 use Drupal\Core\Hook\Attribute\StopProceduralHookScan;
 use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
-use Symfony\Component\DependencyInjection\Definition;
 
 /**
  * Collects and registers hook implementations.
@@ -224,6 +223,43 @@ protected static function registerImplementations(
       }
     }
 
+    $implementationsByHook = static::calculateImplementations(
+      $implementations,
+      $collector,
+      $orderExtraTypes,
+      $hookOrderOperations,
+    );
+
+    static::writeImplementationsToContainer($container, $implementationsByHook);
+
+    // Update the module handler definition.
+    $definition = $container->getDefinition('module_handler');
+    $definition->setArgument('$groupIncludes', $groupIncludes);
+    $definition->setArgument('$orderedExtraTypes', $orderExtraTypes);
+  }
+
+  /**
+   * Calculates the ordered implementations.
+   *
+   * @param array<string, array<string, array<class-string, list<string>>>> $implementations
+   *   All implementations, as method names keyed by hook, module and class.
+   * @param \Drupal\Core\Hook\HookCollectorPass $collector
+   *   The collector.
+   * @param array<string, list<string>> $orderExtraTypes
+   *   Extra types to order a hook with.
+   * @param list<\Drupal\Core\Hook\HookOperation> $hookOrderOperations
+   *   All attributes that contain ordering information.
+   *
+   * @return array<string, array<string, string>>
+   *   Implementations, as module names keyed by hook name and "$class::$method"
+   *   identifier.
+   */
+  protected static function calculateImplementations(
+    array $implementations,
+    self $collector,
+    array $orderExtraTypes,
+    array $hookOrderOperations,
+  ): array {
     // List of hooks and modules formatted for hook_module_implements_alter().
     $moduleImplementsMap = [];
     foreach ($implementations as $hook => $implementationsByModule) {
@@ -232,7 +268,7 @@ protected static function registerImplementations(
       }
     }
 
-    $alteredImplementations = [];
+    $implementationsByHook = [];
     foreach ($moduleImplementsMap as $hook => $moduleImplements) {
       $extraHooks = $orderExtraTypes[$hook] ?? [];
       // Add implementations to the array we pass to legacy ordering
@@ -247,15 +283,113 @@ protected static function registerImplementations(
       foreach ($moduleImplements as $module => $v) {
         foreach ($implementations[$hook][$module] ?? [] as $class => $methods) {
           foreach ($methods as $method) {
-            $alteredImplementations[$hook]["$class::$method"] = $module;
+            $implementationsByHook[$hook]["$class::$method"] = $module;
           }
         }
+        if (count($extraHooks) > 1) {
+          $combinedHook = implode(':', $extraHooks);
+          foreach ($extraHooks as $extraHook) {
+            foreach ($implementations[$extraHook][$module] ?? [] as $class => $methods) {
+              foreach ($methods as $method) {
+                $implementationsByHook[$combinedHook]["$class::$method"] = $module;
+              }
+            }
+          }
+        }
+      }
+    }
+
+    foreach ($hookOrderOperations as $hookOrderOperation) {
+      static::applyOrderAttributeOperation(
+        $implementationsByHook,
+        $orderExtraTypes,
+        $hookOrderOperation,
+      );
+    }
+
+    return $implementationsByHook;
+  }
+
+  /**
+   * Applies hook order changes from a single attribute with order information.
+   *
+   * @param array<string, array<string, string>> $implementationsByHook
+   *   Implementations, as module names keyed by hook name and "$class::$method"
+   *   identifier.
+   * @param array<string, list<string>> $orderExtraTypes
+   *   Extra types to order a hook with.
+   * @param \Drupal\Core\Hook\HookOperation $hookOrderOperation
+   *   Hook attribute with order information.
+   */
+  protected static function applyOrderAttributeOperation(
+    array &$implementationsByHook,
+    array $orderExtraTypes,
+    HookOperation $hookOrderOperation,
+  ): void {
+    // ::process() adds the hook serving as key to the order extraTypes so it
+    // does not need to be added if there's a extraTypes for the hook.
+    $hooks = $orderExtraTypes[$hookOrderOperation->hook] ?? [$hookOrderOperation->hook];
+    $combinedHook = implode(':', $hooks);
+    $identifier = $hookOrderOperation->class . '::' . $hookOrderOperation->method;
+    $module = $implementationsByHook[$combinedHook][$identifier] ?? NULL;
+    if ($module === NULL) {
+      // Implementation is not in the list. Nothing to reorder.
+      return;
+    }
+    $list = $implementationsByHook[$combinedHook];
+    $order = $hookOrderOperation->order;
+    if ($order === NULL) {
+      throw new \InvalidArgumentException('This method must only be called with attributes that have order information.');
+    }
+    if ($order === Order::First) {
+      unset($list[$identifier]);
+      $list = [$identifier => $module] + $list;
+    }
+    elseif ($order === Order::Last) {
+      unset($list[$identifier]);
+      $list[$identifier] = $module;
+    }
+    elseif ($order instanceof ComplexOrder) {
+      $shouldBeAfter = !$order->value;
+      unset($list[$identifier]);
+      $identifiers = array_keys($list);
+      $modules = array_values($list);
+      $compareIndices = [];
+      if (isset($hookOrderOperation->order->modules)) {
+        $compareIndices = array_keys(array_intersect($modules, $hookOrderOperation->order->modules));
+      }
+      foreach ($hookOrderOperation->order->classesAndMethods as [$otherClass, $otherMethod]) {
+        $compareIndices[] = array_search("$otherClass::$otherMethod", $identifiers, TRUE);
+      }
+      if (!$compareIndices) {
+        return;
       }
+      $splice_index = $shouldBeAfter
+        ? max($compareIndices) + 1
+        : min($compareIndices);
+      array_splice($identifiers, $splice_index, 0, [$identifier]);
+      array_splice($modules, $splice_index, 0, [$module]);
+      $list = array_combine($identifiers, $modules);
     }
+    $implementationsByHook[$combinedHook] = $list;
+  }
 
+  /**
+   * Writes all implementations to the container.
+   *
+   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
+   *   The container builder.
+   * @param array<string, array<string, string>> $implementationsByHook
+   *   Implementations, as module names keyed by hook name and "$class::$method"
+   *   identifier.
+   */
+  protected static function writeImplementationsToContainer(
+    ContainerBuilder $container,
+    array $implementationsByHook,
+  ): void {
     $map = [];
     $tagsInfoByClass = [];
-    foreach ($alteredImplementations as $hook => $hookImplementations) {
+    foreach ($implementationsByHook as $hook => $hookImplementations) {
       $priority = 0;
       foreach ($hookImplementations as $class_and_method => $module) {
         [$class, $method] = explode('::', $class_and_method);
@@ -283,140 +417,7 @@ protected static function registerImplementations(
       }
     }
 
-    // Pass necessary parameters to moduleHandler.
-    $definition = $container->getDefinition('module_handler');
-    $definition->setArgument('$groupIncludes', $groupIncludes);
-    $definition->setArgument('$orderedExtraTypes', $orderExtraTypes);
     $container->setParameter('hook_implementations_map', $map);
-
-    foreach ($hookOrderOperations as $hookOrderOperation) {
-      assert($hookOrderOperation instanceof HookOperation);
-      // ::process() adds the hook serving as key to the order extraTypes so it
-      // does not need to be added if there's a extraTypes for the hook.
-      $hooks = $orderExtraTypes[$hookOrderOperation->hook] ?? [$hookOrderOperation->hook];
-      $combinedHook = implode(':', $hooks);
-      if ($hookOrderOperation->order instanceof ComplexOrder) {
-        // Collect classes and methods for
-        // self::registerComplexHookImplementations().
-        $classesAndMethods = $hookOrderOperation->order->classesAndMethods;
-        foreach ($hookOrderOperation->order->modules as $module) {
-          foreach ($hooks as $hook) {
-            foreach ($implementations[$hook][$module] ?? [] as $class => $methods) {
-              foreach ($methods as $method) {
-                $classesAndMethods[] = [$class, $method];
-              }
-            }
-          }
-        }
-        // Verify the correct structure of
-        // $hookOrderOperation->order->classesAndMethods and create specifiers
-        // for HookPriority::change() while at it.
-        $otherSpecifiers = array_map(
-          static function ($pair) {
-            if (!is_array($pair)) {
-              return throw new \LogicException('classesAndMethods needs to be an array of arrays');
-            }
-            return $pair[0] . '::' . $pair[1];
-          },
-          $classesAndMethods,
-        );
-        if (count($hooks) > 1) {
-          // The hook implementation in $hookOrderOperation and everything in
-          // $classesAndMethods will be ordered relative to each other as if
-          // they were implementing a single hook. This needs to be marked on
-          // their service definition and added to the
-          // hook_implementations_map container parameter.
-          $classesAndMethods[] = [$hookOrderOperation->class, $hookOrderOperation->method];
-
-          $map1 = $container->getParameter('hook_implementations_map');
-          $priority1 = 0;
-          foreach ($classesAndMethods as [$class1, $method1]) {
-            // Ordering against not installed modules is possible.
-            if (isset($moduleFinder[$class1][$method1])) {
-              if (count(array_unique($moduleFinder[$class1][$method1])) > 1) {
-                throw new \LogicException('Complex ordering can only work when all implementations on a single method are for the same module.');
-              }
-              $map1[$combinedHook][$class1][$method1] = reset($moduleFinder[$class1][$method1]);
-              $container->findDefinition($class1)->addTag('kernel.event_listener', [
-                'event' => "drupal_hook.$combinedHook",
-                'method' => $method1,
-                'priority' => $priority1--,
-              ]);
-            }
-          }
-          $container->setParameter('hook_implementations_map', $map1);
-        }
-      }
-      else {
-        $otherSpecifiers = NULL;
-      }
-
-      foreach ($container->findTaggedServiceIds('kernel.event_listener') as $id => $tags) {
-        foreach ($tags as $key => $tag) {
-          if ($tag['event'] === "drupal_hook.$combinedHook") {
-            $index = "$id.$key";
-            $priority = $tag['priority'];
-            // Symfony documents event listener priorities to be integers,
-            // HookCollectorPass sets them to be integers, ::set() only
-            // accepts integers.
-            assert(is_int($priority));
-            $priorities[$index] = $priority;
-            $specifier = "$id::" . $tag['method'];
-            if ($specifier === "$hookOrderOperation->class::$hookOrderOperation->method") {
-              $index_this = $index;
-            }
-            // $other_specifiers is defined for before and after, for these
-            // compare only the priority of those. For first and last the
-            // priority of every other hook matters.
-            elseif (!isset($otherSpecifiers) || in_array($specifier, $otherSpecifiers)) {
-              $priorities_other[$specifier] = $priority;
-            }
-          }
-        }
-      }
-      if (!isset($index_this) || !isset($priorities) || !isset($priorities_other)) {
-        return;
-      }
-
-      $shouldBeLarger = (bool) $hookOrderOperation->order->value;
-      // The priority of the hook being changed.
-      $priority_this = $priorities[$index_this];
-      // The priority of the hook being compared to.
-      $priority_other = $shouldBeLarger ? max($priorities_other) : min($priorities_other);
-      // If the order is correct there is nothing to do. If the two priorities
-      // are the same then the order is undefined and so it can't be correct.
-      // If they are not the same and $priority_this is already larger exactly
-      // when $shouldBeLarger says then it's the correct order.
-      if ($priority_this !== $priority_other && ($shouldBeLarger === ($priority_this > $priority_other))) {
-        return;
-      }
-      $priority_new = $priority_other + ($shouldBeLarger ? 1 : -1);
-      // For first and last this new priority is already larger/smaller
-      // than all existing priorities but for before / after it might belong to
-      // an already existing hook. In this case set the new priority temporarily
-      // to be halfway between $priority_other and $priority_new then give all
-      // hook implementations new, integer priorities keeping this new order.
-      // This ensures the hook implementation being changed is in the right order
-      // relative to both $priority_other and the hook whose priority was
-      // $priority_new.
-      if (in_array($priority_new, $priorities)) {
-        $priorities[$index_this] = $priority_other + ($shouldBeLarger ? 0.5 : -0.5);
-        asort($priorities);
-        $changed_indexes = array_keys($priorities);
-        $priorities = array_combine($changed_indexes, range(1, count($changed_indexes)));
-      }
-      else {
-        $priorities[$index_this] = $priority_new;
-        $changed_indexes = [$index_this];
-      }
-      foreach ($changed_indexes as $index) {
-        [$id1, $key1] = explode('.', $index);
-        $definition = $container->findDefinition($id1);
-        $tags = $definition->getTags();
-        $tags['kernel.event_listener'][$key1]['priority'] = $priorities[$index];
-        $definition->setTags($tags);
-      }
-    }
   }
 
   /**
-- 
GitLab


From 83f0ab0c8a7fca4b8417d6ff6bf9c16c4c6fad35 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Fri, 7 Mar 2025 01:20:12 +0100
Subject: [PATCH 163/173] More elegant array merge in ModuleHandler.

---
 core/lib/Drupal/Core/Extension/ModuleHandler.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php
index 71e74ecaf899..c9123822cb32 100644
--- a/core/lib/Drupal/Core/Extension/ModuleHandler.php
+++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php
@@ -452,7 +452,7 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) {
         // for ordering because the set might contain hooks not included in
         // this alter() call. \Drupal\Core\Hook\HookPriority::change()
         // registers the implementations of combined hooks.
-        foreach (array_merge($extra_hooks, [$type . '_alter']) as $extra_hook) {
+        foreach ([...$extra_hooks, $hook] as $extra_hook) {
           if (isset($this->orderedExtraTypes[$extra_hook])) {
             $orderedHooks = $this->orderedExtraTypes[$extra_hook];
             $extra_listeners = $this->findListenersForAlter(implode(':', $orderedHooks));
-- 
GitLab


From 0d5248785b8be0f85cf797cb15e45251d2f38b5a Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Fri, 7 Mar 2025 13:39:43 +0100
Subject: [PATCH 164/173] Collect different attribute types separately.

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 107 ++++++++----------
 1 file changed, 45 insertions(+), 62 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 96806ded7509..d5b9f5a86106 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -59,13 +59,21 @@ class HookCollectorPass implements CompilerPassInterface {
   private array $groupIncludes = [];
 
   /**
-   * A list of attributes for hook implementations.
+   * Implementations, as module names keyed by hook name and "$class::$method".
    *
-   * Keys are module, class and method. Values are Hook attributes.
-   *
-   * @var array<string, array<class-string, array<string, list<\Drupal\Core\Hook\HookOperation>>>>
+   * @var array<string, array<string, string>>
+   */
+  protected array $implementations = [];
+
+  /**
+   * @var array<int, list<\Drupal\Core\Hook\HookOperation>>
+   */
+  protected array $orderAttributesByPhase = [0 => [], 1 => []];
+
+  /**
+   * @var list<\Drupal\Core\Hook\Attribute\RemoveHook>
    */
-  protected array $moduleHooks = [];
+  protected array $removeHookAttributes = [];
 
   /**
    * {@inheritdoc}
@@ -82,46 +90,12 @@ public function process(ContainerBuilder $container): array {
     // Hooks that should be ordered together when extra types are involved.
     $orderExtraTypes = [];
 
-    // Hook attributes that contain ordering information.
-    $hookOrderOperations = [];
-
-    // List of modules that the hooks are defined for, keyed by class and
-    // method.
-    $moduleFinder = [];
-
-    // These attributes need to be processed after all hooks have been
-    // processed.
-    $processAfter = [
-      RemoveHook::class => [],
-      ReOrderHook::class => [],
-    ];
-    foreach (array_keys($container->getParameter('container.modules')) as $module) {
-      foreach ($collector->moduleHooks[$module] ?? [] as $class => $attributesByMethod) {
-        foreach ($attributesByMethod as $method => $attributes) {
-          foreach ($attributes as $hookAttribute) {
-            assert($hookAttribute instanceof HookOperation);
-            if (isset($processAfter[get_class($hookAttribute)])) {
-              $processAfter[get_class($hookAttribute)][] = $hookAttribute;
-              continue;
-            }
-            if (!$hookAttribute instanceof Hook) {
-              // This is an unsupported attribute class, the code below would
-              // not work.
-              continue;
-            }
-            if ($class !== ProceduralCall::class) {
-              self::checkForProceduralOnlyHooks($hookAttribute, $class);
-            }
-            // Set properties on hook class that are needed for registration.
-            $hookAttribute->set($class, $module, $method);
-            // Store the implementation details for registering the hook.
-            $implementations[$hookAttribute->hook][$hookAttribute->module][$class][$hookAttribute->method] = $hookAttribute->method;
-            // Reverse lookup for modules implementing hooks.
-            $moduleFinder[$class][$hookAttribute->method][$hookAttribute->hook] = $hookAttribute->module;
-            if ($hookAttribute->order) {
-              $hookOrderOperations[] = $hookAttribute;
-            }
-          }
+    $modules = array_keys($container->getParameter('container.modules'));
+    foreach ($collector->implementations as $hook => $hookImplementations) {
+      foreach ($modules as $module) {
+        foreach (array_keys($hookImplementations, $module, TRUE) as $identifier) {
+          [$class, $method] = explode('::', $identifier);
+          $implementations[$hook][$module][$class][$method] = $method;
         }
       }
     }
@@ -130,8 +104,9 @@ public function process(ContainerBuilder $container): array {
     // registering the hooks. This must happen after all collection, but before
     // registration to ensure the hook it is removing has already been
     // discovered.
-    foreach ($processAfter[RemoveHook::class] as $removeHook) {
-      if ($module = ($moduleFinder[$removeHook->class][$removeHook->method][$removeHook->hook] ?? '')) {
+    foreach ($collector->removeHookAttributes as $removeHook) {
+      $module = $collector->implementations[$removeHook->hook][$removeHook->class . '::' . $removeHook->method] ?? NULL;
+      if ($module !== NULL) {
         // Remove the hook implementation for the defined class, method, and
         // hook.
         unset($implementations[$removeHook->hook][$module][$removeHook->class][$removeHook->method]);
@@ -155,10 +130,8 @@ public function process(ContainerBuilder $container): array {
     // before registering the hooks. This must happen after all collection,
     // but before registration to ensure this ordering directive takes
     // precedence.
-    foreach ($processAfter[ReOrderHook::class] as $reOrderHook) {
-      $hookOrderOperations[] = $reOrderHook;
-    }
-
+    /** @var list<\Drupal\Core\Hook\HookOperation> $hookOrderOperations */
+    $hookOrderOperations = array_merge(...$collector->orderAttributesByPhase);
     foreach ($hookOrderOperations as $hookWithOrder) {
       if ($hookWithOrder->order instanceof ComplexOrder && $hookWithOrder->order->extraTypes) {
         $extraTypes = [... $hookWithOrder->order->extraTypes, $hookWithOrder->hook];
@@ -173,7 +146,7 @@ public function process(ContainerBuilder $container): array {
     // is removed.
     // @see https://www.drupal.org/project/drupal/issues/3481778
     if (count($container->getDefinitions()) > 1) {
-      static::registerImplementations($container, $collector, $implementations, $orderExtraTypes, $hookOrderOperations, $moduleFinder);
+      static::registerImplementations($container, $collector, $implementations, $orderExtraTypes, $hookOrderOperations);
     }
     return $implementations;
   }
@@ -193,12 +166,6 @@ public function process(ContainerBuilder $container): array {
    *   Extra types to order a hook with.
    * @param list<\Drupal\Core\Hook\HookOperation> $hookOrderOperations
    *   All attributes that contain ordering information.
-   * @param array<class-string, array<string, array<string, string>>> $moduleFinder
-   *   Lookup map to find the module for each hook implementation.
-   *   Array keys are the class, method, and hook, array values are module
-   *   names.
-   *   The module name can be different from the module the class is in,
-   *   because an implementation can be on behalf of another module.
    */
   protected static function registerImplementations(
     ContainerBuilder $container,
@@ -206,7 +173,6 @@ protected static function registerImplementations(
     array $implementations,
     array $orderExtraTypes,
     array $hookOrderOperations,
-    array $moduleFinder,
   ): void {
     $container->register(ProceduralCall::class, ProceduralCall::class)
       ->addArgument($collector->includes);
@@ -506,7 +472,24 @@ protected function collectModuleHookImplementations($dir, $module, $module_preg,
             $hook_file_cache->set($filename, ['class' => $class, 'attributes' => $attributes]);
           }
         }
-        $this->moduleHooks[$module][$class] = $attributes;
+        foreach ($attributes as $method => $methodAttributes) {
+          foreach ($methodAttributes as $attribute) {
+            if ($attribute instanceof Hook) {
+              self::checkForProceduralOnlyHooks($attribute, $class);
+              $this->implementations[$attribute->hook][$class . '::' . ($attribute->method ?: $method)] = $attribute->module ?? $module;
+              if ($attribute->order !== NULL) {
+                $attribute->set($class, $attribute->module ?? $module, $method);
+                $this->orderAttributesByPhase[0][] = $attribute;
+              }
+            }
+            elseif ($attribute instanceof ReOrderHook) {
+              $this->orderAttributesByPhase[1][] = $attribute;
+            }
+            elseif ($attribute instanceof RemoveHook) {
+              $this->removeHookAttributes[] = $attribute;
+            }
+          }
+        }
       }
       elseif (!$skip_procedural) {
         $implementations = $procedural_hook_file_cache->get($filename);
@@ -570,15 +553,15 @@ protected static function filterIterator(\SplFileInfo $fileInfo, $key, \Recursiv
    *   The name of function implementing the hook.
    */
   protected function addProceduralImplementation(\SplFileInfo $fileinfo, string $hook, string $module, string $function): void {
-    $this->moduleHooks[$module][ProceduralCall::class][$function] = [new Hook($hook, method: $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->implementations[$hook][ProceduralCall::class . '::' . $module . '_' . $hook] = $module;
     if ($fileinfo->getExtension() !== 'module') {
       $this->includes[$function] = $fileinfo->getPathname();
     }
-- 
GitLab


From 722d51cf9033ad7d5da602afbacbc1d03579aa09 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Fri, 7 Mar 2025 13:55:57 +0100
Subject: [PATCH 165/173] Extract getOrderExtraTypes from ::process().

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 40 ++++++++++++++-----
 1 file changed, 29 insertions(+), 11 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index d5b9f5a86106..76e11d68f68a 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -87,9 +87,6 @@ public function process(ContainerBuilder $container): array {
     // List of modules implementing hooks with the implementation details.
     $implementations = [];
 
-    // Hooks that should be ordered together when extra types are involved.
-    $orderExtraTypes = [];
-
     $modules = array_keys($container->getParameter('container.modules'));
     foreach ($collector->implementations as $hook => $hookImplementations) {
       foreach ($modules as $module) {
@@ -132,6 +129,34 @@ public function process(ContainerBuilder $container): array {
     // precedence.
     /** @var list<\Drupal\Core\Hook\HookOperation> $hookOrderOperations */
     $hookOrderOperations = array_merge(...$collector->orderAttributesByPhase);
+    $orderExtraTypes = $collector->getOrderExtraTypes($hookOrderOperations);
+
+    // @todo investigate whether this if() is needed after ModuleHandler::add()
+    // is removed.
+    // @see https://www.drupal.org/project/drupal/issues/3481778
+    if (count($container->getDefinitions()) > 1) {
+      static::registerImplementations($container, $collector, $implementations, $orderExtraTypes, $hookOrderOperations);
+    }
+    return $implementations;
+  }
+
+  /**
+   * Gets groups of extra hooks from collected data.
+   *
+   * @param list<\Drupal\Core\Hook\HookOperation> $hookOrderOperations
+   *   All attributes that contain ordering information.
+   *
+   * @return array<string, list<string>>
+   *   Lists of extra hooks keyed by main hook.
+   */
+  protected function getOrderExtraTypes(array $hookOrderOperations): array {
+    // Loop over all ReOrderHook attributes and gather order information
+    // before registering the hooks. This must happen after all collection,
+    // but before registration to ensure this ordering directive takes
+    // precedence.
+    /** @var list<\Drupal\Core\Hook\HookOperation> $hookOrderOperations */
+    $hookOrderOperations = array_merge(...$this->orderAttributesByPhase);
+    $orderExtraTypes = [];
     foreach ($hookOrderOperations as $hookWithOrder) {
       if ($hookWithOrder->order instanceof ComplexOrder && $hookWithOrder->order->extraTypes) {
         $extraTypes = [... $hookWithOrder->order->extraTypes, $hookWithOrder->hook];
@@ -141,14 +166,7 @@ public function process(ContainerBuilder $container): array {
       }
     }
     $orderExtraTypes = array_map('array_unique', $orderExtraTypes);
-
-    // @todo investigate whether this if() is needed after ModuleHandler::add()
-    // is removed.
-    // @see https://www.drupal.org/project/drupal/issues/3481778
-    if (count($container->getDefinitions()) > 1) {
-      static::registerImplementations($container, $collector, $implementations, $orderExtraTypes, $hookOrderOperations);
-    }
-    return $implementations;
+    return array_map('array_values', $orderExtraTypes);
   }
 
   /**
-- 
GitLab


From 65366f1a1efa23f2ab1670dd52a77ef115e1625b Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Fri, 7 Mar 2025 14:01:04 +0100
Subject: [PATCH 166/173] Collapse registerImplementations() into ::process().

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 87 +++++++------------
 1 file changed, 32 insertions(+), 55 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 76e11d68f68a..924768284c59 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -134,9 +134,39 @@ public function process(ContainerBuilder $container): array {
     // @todo investigate whether this if() is needed after ModuleHandler::add()
     // is removed.
     // @see https://www.drupal.org/project/drupal/issues/3481778
-    if (count($container->getDefinitions()) > 1) {
-      static::registerImplementations($container, $collector, $implementations, $orderExtraTypes, $hookOrderOperations);
+    if (count($container->getDefinitions()) <= 1) {
+      return $implementations;
     }
+
+    $container->register(ProceduralCall::class, ProceduralCall::class)
+      ->addArgument($collector->includes);
+
+    // Gather includes for each hook_hook_info group.
+    // We store this in $groupIncludes so moduleHandler can ensure the files
+    // are included runtime when the hooks are invoked.
+    $groupIncludes = [];
+    foreach ($collector->hookInfo as $function) {
+      foreach ($function() as $hook => $info) {
+        if (isset($collector->groupIncludes[$info['group']])) {
+          $groupIncludes[$hook] = $collector->groupIncludes[$info['group']];
+        }
+      }
+    }
+
+    $implementationsByHook = static::calculateImplementations(
+      $implementations,
+      $collector,
+      $orderExtraTypes,
+      $hookOrderOperations,
+    );
+
+    static::writeImplementationsToContainer($container, $implementationsByHook);
+
+    // Update the module handler definition.
+    $definition = $container->getDefinition('module_handler');
+    $definition->setArgument('$groupIncludes', $groupIncludes);
+    $definition->setArgument('$orderedExtraTypes', $orderExtraTypes);
+
     return $implementations;
   }
 
@@ -169,59 +199,6 @@ protected function getOrderExtraTypes(array $hookOrderOperations): array {
     return array_map('array_values', $orderExtraTypes);
   }
 
-  /**
-   * Register hook implementations as event listeners.
-   *
-   * Passes required include and ordering information to module_handler.
-   *
-   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
-   *   The container.
-   * @param \Drupal\Core\Hook\HookCollectorPass $collector
-   *   The collector.
-   * @param array<string, array<string, array<class-string, list<string>>>> $implementations
-   *   All implementations, as method names keyed by hook, module and class.
-   * @param array<string, list<string>> $orderExtraTypes
-   *   Extra types to order a hook with.
-   * @param list<\Drupal\Core\Hook\HookOperation> $hookOrderOperations
-   *   All attributes that contain ordering information.
-   */
-  protected static function registerImplementations(
-    ContainerBuilder $container,
-    HookCollectorPass $collector,
-    array $implementations,
-    array $orderExtraTypes,
-    array $hookOrderOperations,
-  ): void {
-    $container->register(ProceduralCall::class, ProceduralCall::class)
-      ->addArgument($collector->includes);
-
-    // Gather includes for each hook_hook_info group.
-    // We store this in $groupIncludes so moduleHandler can ensure the files
-    // are included runtime when the hooks are invoked.
-    $groupIncludes = [];
-    foreach ($collector->hookInfo as $function) {
-      foreach ($function() as $hook => $info) {
-        if (isset($collector->groupIncludes[$info['group']])) {
-          $groupIncludes[$hook] = $collector->groupIncludes[$info['group']];
-        }
-      }
-    }
-
-    $implementationsByHook = static::calculateImplementations(
-      $implementations,
-      $collector,
-      $orderExtraTypes,
-      $hookOrderOperations,
-    );
-
-    static::writeImplementationsToContainer($container, $implementationsByHook);
-
-    // Update the module handler definition.
-    $definition = $container->getDefinition('module_handler');
-    $definition->setArgument('$groupIncludes', $groupIncludes);
-    $definition->setArgument('$orderedExtraTypes', $orderExtraTypes);
-  }
-
   /**
    * Calculates the ordered implementations.
    *
-- 
GitLab


From f7cb2500c3f7492a9021499a0e879d5c61e7486d Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Fri, 7 Mar 2025 14:57:45 +0100
Subject: [PATCH 167/173] Don't use the nested array $implementations for
 calculations.

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 72 +++++++------------
 1 file changed, 27 insertions(+), 45 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 924768284c59..706229b25874 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -84,11 +84,21 @@ class HookCollectorPass implements CompilerPassInterface {
   public function process(ContainerBuilder $container): array {
     $collector = static::collectAllHookImplementations($container->getParameter('container.modules'), $container);
 
+    $implementationsByHook = $collector->implementations;
+
+    // Loop over all RemoveHook attributes and remove them from the maps before
+    // registering the hooks. This must happen after all collection, but before
+    // registration to ensure the hook it is removing has already been
+    // discovered.
+    foreach ($collector->removeHookAttributes as $removeHook) {
+      unset($implementationsByHook[$removeHook->hook][$removeHook->class . '::' . $removeHook->method]);
+    }
+
     // List of modules implementing hooks with the implementation details.
     $implementations = [];
 
     $modules = array_keys($container->getParameter('container.modules'));
-    foreach ($collector->implementations as $hook => $hookImplementations) {
+    foreach ($implementationsByHook as $hook => $hookImplementations) {
       foreach ($modules as $module) {
         foreach (array_keys($hookImplementations, $module, TRUE) as $identifier) {
           [$class, $method] = explode('::', $identifier);
@@ -97,30 +107,11 @@ public function process(ContainerBuilder $container): array {
       }
     }
 
-    // Loop over all RemoveHook attributes and remove them from the maps before
-    // registering the hooks. This must happen after all collection, but before
-    // registration to ensure the hook it is removing has already been
-    // discovered.
-    foreach ($collector->removeHookAttributes as $removeHook) {
-      $module = $collector->implementations[$removeHook->hook][$removeHook->class . '::' . $removeHook->method] ?? NULL;
-      if ($module !== NULL) {
-        // Remove the hook implementation for the defined class, method, and
-        // hook.
-        unset($implementations[$removeHook->hook][$module][$removeHook->class][$removeHook->method]);
-        // Remove empty arrays, after the entry was removed.
-        // Hook implementation removal is expected to be rare, therefore it will
-        // be faster to do it like this than cleaning the entire tree
-        // afterwards.
-        if (empty($implementations[$removeHook->hook][$module][$removeHook->class])) {
-          unset($implementations[$removeHook->hook][$module][$removeHook->class]);
-          if (empty($implementations[$removeHook->hook][$module])) {
-            unset($implementations[$removeHook->hook][$module]);
-            if (empty($implementations[$removeHook->hook])) {
-              unset($implementations[$removeHook->hook]);
-            }
-          }
-        }
-      }
+    // @todo investigate whether this if() is needed after ModuleHandler::add()
+    // is removed.
+    // @see https://www.drupal.org/project/drupal/issues/3481778
+    if (count($container->getDefinitions()) <= 1) {
+      return $implementations;
     }
 
     // Loop over all ReOrderHook attributes and gather order information
@@ -131,13 +122,6 @@ public function process(ContainerBuilder $container): array {
     $hookOrderOperations = array_merge(...$collector->orderAttributesByPhase);
     $orderExtraTypes = $collector->getOrderExtraTypes($hookOrderOperations);
 
-    // @todo investigate whether this if() is needed after ModuleHandler::add()
-    // is removed.
-    // @see https://www.drupal.org/project/drupal/issues/3481778
-    if (count($container->getDefinitions()) <= 1) {
-      return $implementations;
-    }
-
     $container->register(ProceduralCall::class, ProceduralCall::class)
       ->addArgument($collector->includes);
 
@@ -154,7 +138,7 @@ public function process(ContainerBuilder $container): array {
     }
 
     $implementationsByHook = static::calculateImplementations(
-      $implementations,
+      $implementationsByHook,
       $collector,
       $orderExtraTypes,
       $hookOrderOperations,
@@ -202,7 +186,9 @@ protected function getOrderExtraTypes(array $hookOrderOperations): array {
   /**
    * Calculates the ordered implementations.
    *
-   * @param array<string, array<string, array<class-string, list<string>>>> $implementations
+   * @param array<string, array<string, string>> $implementationsByHookOrig
+   *   Implementations before ordering, as module names keyed by hook name and
+   *   "$class::$method" identifier.
    *   All implementations, as method names keyed by hook, module and class.
    * @param \Drupal\Core\Hook\HookCollectorPass $collector
    *   The collector.
@@ -216,15 +202,15 @@ protected function getOrderExtraTypes(array $hookOrderOperations): array {
    *   identifier.
    */
   protected static function calculateImplementations(
-    array $implementations,
+    array $implementationsByHookOrig,
     self $collector,
     array $orderExtraTypes,
     array $hookOrderOperations,
   ): array {
     // List of hooks and modules formatted for hook_module_implements_alter().
     $moduleImplementsMap = [];
-    foreach ($implementations as $hook => $implementationsByModule) {
-      foreach ($implementationsByModule as $module => $implementationsByClass) {
+    foreach ($implementationsByHookOrig as $hook => $hookImplementations) {
+      foreach ($hookImplementations as $module) {
         $moduleImplementsMap[$hook][$module] = '';
       }
     }
@@ -242,18 +228,14 @@ protected static function calculateImplementations(
         $alter($moduleImplements, $hook);
       }
       foreach ($moduleImplements as $module => $v) {
-        foreach ($implementations[$hook][$module] ?? [] as $class => $methods) {
-          foreach ($methods as $method) {
-            $implementationsByHook[$hook]["$class::$method"] = $module;
-          }
+        foreach (array_keys($implementationsByHookOrig[$hook], $module, TRUE) as $identifier) {
+          $implementationsByHook[$hook][$identifier] = $module;
         }
         if (count($extraHooks) > 1) {
           $combinedHook = implode(':', $extraHooks);
           foreach ($extraHooks as $extraHook) {
-            foreach ($implementations[$extraHook][$module] ?? [] as $class => $methods) {
-              foreach ($methods as $method) {
-                $implementationsByHook[$combinedHook]["$class::$method"] = $module;
-              }
+            foreach (array_keys($implementationsByHookOrig[$extraHook] ?? [], $module, TRUE) as $identifier) {
+              $implementationsByHook[$combinedHook][$identifier] = $module;
             }
           }
         }
-- 
GitLab


From dc9830f4dee2158264b14ede3f3f3fc42771fd9a Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Fri, 7 Mar 2025 15:07:26 +0100
Subject: [PATCH 168/173] Don't call ->process() from ->getImplementations().

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 70 +++++++++----------
 1 file changed, 35 insertions(+), 35 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 706229b25874..bc464a1b7aa0 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -77,42 +77,11 @@ class HookCollectorPass implements CompilerPassInterface {
 
   /**
    * {@inheritdoc}
-   *
-   * @return array<string, array<string, array<class-string, array<string, string>>>>
-   *   Hook implementation method names keyed by hook, module, class and method.
    */
-  public function process(ContainerBuilder $container): array {
+  public function process(ContainerBuilder $container): void {
     $collector = static::collectAllHookImplementations($container->getParameter('container.modules'), $container);
 
-    $implementationsByHook = $collector->implementations;
-
-    // Loop over all RemoveHook attributes and remove them from the maps before
-    // registering the hooks. This must happen after all collection, but before
-    // registration to ensure the hook it is removing has already been
-    // discovered.
-    foreach ($collector->removeHookAttributes as $removeHook) {
-      unset($implementationsByHook[$removeHook->hook][$removeHook->class . '::' . $removeHook->method]);
-    }
-
-    // List of modules implementing hooks with the implementation details.
-    $implementations = [];
-
-    $modules = array_keys($container->getParameter('container.modules'));
-    foreach ($implementationsByHook as $hook => $hookImplementations) {
-      foreach ($modules as $module) {
-        foreach (array_keys($hookImplementations, $module, TRUE) as $identifier) {
-          [$class, $method] = explode('::', $identifier);
-          $implementations[$hook][$module][$class][$method] = $method;
-        }
-      }
-    }
-
-    // @todo investigate whether this if() is needed after ModuleHandler::add()
-    // is removed.
-    // @see https://www.drupal.org/project/drupal/issues/3481778
-    if (count($container->getDefinitions()) <= 1) {
-      return $implementations;
-    }
+    $implementationsByHook = $collector->getFilteredImplementations();
 
     // Loop over all ReOrderHook attributes and gather order information
     // before registering the hooks. This must happen after all collection,
@@ -150,8 +119,21 @@ public function process(ContainerBuilder $container): array {
     $definition = $container->getDefinition('module_handler');
     $definition->setArgument('$groupIncludes', $groupIncludes);
     $definition->setArgument('$orderedExtraTypes', $orderExtraTypes);
+  }
 
-    return $implementations;
+  /**
+   * Gets implementation lists with removals already applied.
+   *
+   * @return array<string, list<string>>
+   *   Implementations, as module names keyed by hook name and
+   *   "$class::$method".
+   */
+  protected function getFilteredImplementations(): array {
+    $implementationsByHook = $this->implementations;
+    foreach ($this->removeHookAttributes as $removeHook) {
+      unset($implementationsByHook[$removeHook->hook][$removeHook->class . '::' . $removeHook->method]);
+    }
+    return $implementationsByHook;
   }
 
   /**
@@ -569,7 +551,25 @@ public function loadAllIncludes(): void {
   public function getImplementations(array $paths): array {
     $container = new ContainerBuilder();
     $container->setParameter('container.modules', $paths);
-    return $this->process($container);
+
+    $collector = static::collectAllHookImplementations($container->getParameter('container.modules'), $container);
+
+    $implementationsByHook = $collector->getFilteredImplementations();
+
+    // List of modules implementing hooks with the implementation details.
+    $implementations = [];
+
+    $modules = array_keys($container->getParameter('container.modules'));
+    foreach ($implementationsByHook as $hook => $hookImplementations) {
+      foreach ($modules as $module) {
+        foreach (array_keys($hookImplementations, $module, TRUE) as $identifier) {
+          [$class, $method] = explode('::', $identifier);
+          $implementations[$hook][$module][$class][$method] = $method;
+        }
+      }
+    }
+
+    return $implementations;
   }
 
   /**
-- 
GitLab


From 7cfacabb71e75c09a4f266f0347d947cd249c819 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Fri, 7 Mar 2025 15:39:23 +0100
Subject: [PATCH 169/173] No need to set container.modules anymore.

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index bc464a1b7aa0..479f17c1e643 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -550,16 +550,15 @@ public function loadAllIncludes(): void {
    */
   public function getImplementations(array $paths): array {
     $container = new ContainerBuilder();
-    $container->setParameter('container.modules', $paths);
 
-    $collector = static::collectAllHookImplementations($container->getParameter('container.modules'), $container);
+    $collector = static::collectAllHookImplementations($paths, $container);
 
     $implementationsByHook = $collector->getFilteredImplementations();
 
     // List of modules implementing hooks with the implementation details.
     $implementations = [];
 
-    $modules = array_keys($container->getParameter('container.modules'));
+    $modules = array_keys($paths);
     foreach ($implementationsByHook as $hook => $hookImplementations) {
       foreach ($modules as $module) {
         foreach (array_keys($hookImplementations, $module, TRUE) as $identifier) {
-- 
GitLab


From 31f21274bf7871bab7415608d87eb588a3fa99fa Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Fri, 7 Mar 2025 15:58:50 +0100
Subject: [PATCH 170/173] Don't use the container to get a reflection class.

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 479f17c1e643..eb11d8c21953 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -375,7 +375,7 @@ public static function collectAllHookImplementations(array $module_filenames, ?C
       if ($container?->hasParameter("$module.hooks_converted")) {
         $skip_procedural = $container->getParameter("$module.hooks_converted");
       }
-      $collector->collectModuleHookImplementations(dirname($info['pathname']), $module, $module_preg, $skip_procedural, $container);
+      $collector->collectModuleHookImplementations(dirname($info['pathname']), $module, $module_preg, $skip_procedural);
     }
     return $collector;
   }
@@ -392,10 +392,8 @@ public static function collectAllHookImplementations(array $module_filenames, ?C
    *   matched first.
    * @param bool $skip_procedural
    *   Skip the procedural check for the current module.
-   * @param \Symfony\Component\DependencyInjection\ContainerBuilder|null $container
-   *   The container.
    */
-  protected function collectModuleHookImplementations($dir, $module, $module_preg, bool $skip_procedural, ?ContainerBuilder $container = NULL): void {
+  protected function collectModuleHookImplementations($dir, $module, $module_preg, bool $skip_procedural): void {
     $hook_file_cache = FileCacheFactory::get('hook_implementations');
     $procedural_hook_file_cache = FileCacheFactory::get('procedural_hook_implementations:' . $module_preg);
 
@@ -426,7 +424,7 @@ protected function collectModuleHookImplementations($dir, $module, $module_preg,
           $class = str_replace('/', '\\', $class);
           $attributes = [];
           if (class_exists($class)) {
-            $reflectionClass = $container?->getReflectionClass($class) ?? new \ReflectionClass($class);
+            $reflectionClass = new \ReflectionClass($class);
             $attributes = self::getAttributeInstances($reflectionClass);
             $hook_file_cache->set($filename, ['class' => $class, 'attributes' => $attributes]);
           }
-- 
GitLab


From 3da92f6f6b77b171641e5df525483641e456c87b Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Fri, 7 Mar 2025 16:02:44 +0100
Subject: [PATCH 171/173] Don't create a collector in the collector.

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index eb11d8c21953..2f257673df9a 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -547,11 +547,7 @@ public function loadAllIncludes(): void {
    * @internal
    */
   public function getImplementations(array $paths): array {
-    $container = new ContainerBuilder();
-
-    $collector = static::collectAllHookImplementations($paths, $container);
-
-    $implementationsByHook = $collector->getFilteredImplementations();
+    $implementationsByHook = $this->getFilteredImplementations();
 
     // List of modules implementing hooks with the implementation details.
     $implementations = [];
-- 
GitLab


From 6406c20afb9f17119494f94197e861d9f80f54bf Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Fri, 7 Mar 2025 18:19:32 +0100
Subject: [PATCH 172/173] Array type docs on
 HookCollectorPass::getImplementations().

---
 core/lib/Drupal/Core/Hook/HookCollectorPass.php | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index 2f257673df9a..a88591514b37 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -541,6 +541,12 @@ public function loadAllIncludes(): void {
   /**
    * This method is only to be used by ModuleHandler.
    *
+   * @param array<string, array{pathname: string}> $paths
+   *   Reduced module info arrays by module name.
+   *
+   * @return array<string, array<string, array<class-string, array<string, string>>>>
+   *   Hook implementation method names keyed by hook, module, class and method.
+   *
    * @todo remove when ModuleHandler::add() is removed.
    *   See https://www.drupal.org/project/drupal/issues/3481778
    *
-- 
GitLab


From 81bf66f4ba6dcc0f816330b45651f35607784309 Mon Sep 17 00:00:00 2001
From: Andreas Hennings <andreas@dqxtech.net>
Date: Fri, 7 Mar 2025 17:16:20 +0100
Subject: [PATCH 173/173] Don't pass collectors and containers around.

---
 .../Drupal/Core/Hook/HookCollectorPass.php    | 80 ++++++++-----------
 1 file changed, 34 insertions(+), 46 deletions(-)

diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
index a88591514b37..02d78fe8be4d 100644
--- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php
+++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php
@@ -79,39 +79,42 @@ class HookCollectorPass implements CompilerPassInterface {
    * {@inheritdoc}
    */
   public function process(ContainerBuilder $container): void {
-    $collector = static::collectAllHookImplementations($container->getParameter('container.modules'), $container);
+    $module_list = $container->getParameter('container.modules');
+    $parameters = $container->getParameterBag()->all();
+    $skip_procedural_modules = array_filter(
+      array_keys($module_list),
+      fn (string $module) => !empty($parameters["$module.hooks_converted"]),
+    );
+    $collector = static::collectAllHookImplementations($module_list, $skip_procedural_modules);
 
-    $implementationsByHook = $collector->getFilteredImplementations();
+    $collector->writeToContainer($container);
+  }
 
-    // Loop over all ReOrderHook attributes and gather order information
-    // before registering the hooks. This must happen after all collection,
-    // but before registration to ensure this ordering directive takes
-    // precedence.
-    /** @var list<\Drupal\Core\Hook\HookOperation> $hookOrderOperations */
-    $hookOrderOperations = array_merge(...$collector->orderAttributesByPhase);
-    $orderExtraTypes = $collector->getOrderExtraTypes($hookOrderOperations);
+  /**
+   * Writes collected definitions to the container builder.
+   *
+   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
+   *   Container builder.
+   */
+  protected function writeToContainer(ContainerBuilder $container): void {
+    $orderExtraTypes = $this->getOrderExtraTypes();
 
     $container->register(ProceduralCall::class, ProceduralCall::class)
-      ->addArgument($collector->includes);
+      ->addArgument($this->includes);
 
     // Gather includes for each hook_hook_info group.
     // We store this in $groupIncludes so moduleHandler can ensure the files
     // are included runtime when the hooks are invoked.
     $groupIncludes = [];
-    foreach ($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 = static::calculateImplementations(
-      $implementationsByHook,
-      $collector,
-      $orderExtraTypes,
-      $hookOrderOperations,
-    );
+    $implementationsByHook = $this->calculateImplementations($orderExtraTypes);
 
     static::writeImplementationsToContainer($container, $implementationsByHook);
 
@@ -139,13 +142,10 @@ protected function getFilteredImplementations(): array {
   /**
    * Gets groups of extra hooks from collected data.
    *
-   * @param list<\Drupal\Core\Hook\HookOperation> $hookOrderOperations
-   *   All attributes that contain ordering information.
-   *
    * @return array<string, list<string>>
    *   Lists of extra hooks keyed by main hook.
    */
-  protected function getOrderExtraTypes(array $hookOrderOperations): array {
+  protected function getOrderExtraTypes(): array {
     // Loop over all ReOrderHook attributes and gather order information
     // before registering the hooks. This must happen after all collection,
     // but before registration to ensure this ordering directive takes
@@ -168,27 +168,16 @@ protected function getOrderExtraTypes(array $hookOrderOperations): array {
   /**
    * Calculates the ordered implementations.
    *
-   * @param array<string, array<string, string>> $implementationsByHookOrig
-   *   Implementations before ordering, as module names keyed by hook name and
-   *   "$class::$method" identifier.
-   *   All implementations, as method names keyed by hook, module and class.
-   * @param \Drupal\Core\Hook\HookCollectorPass $collector
-   *   The collector.
    * @param array<string, list<string>> $orderExtraTypes
    *   Extra types to order a hook with.
-   * @param list<\Drupal\Core\Hook\HookOperation> $hookOrderOperations
-   *   All attributes that contain ordering information.
    *
    * @return array<string, array<string, string>>
    *   Implementations, as module names keyed by hook name and "$class::$method"
    *   identifier.
    */
-  protected static function calculateImplementations(
-    array $implementationsByHookOrig,
-    self $collector,
-    array $orderExtraTypes,
-    array $hookOrderOperations,
-  ): array {
+  protected function calculateImplementations(array $orderExtraTypes): array {
+    $implementationsByHookOrig = $this->getFilteredImplementations();
+
     // List of hooks and modules formatted for hook_module_implements_alter().
     $moduleImplementsMap = [];
     foreach ($implementationsByHookOrig as $hook => $hookImplementations) {
@@ -206,7 +195,7 @@ protected static function calculateImplementations(
         $moduleImplements += $moduleImplementsMap[$extraHook] ?? [];
       }
       // Process all hook_module_implements_alter() for build time ordering.
-      foreach ($collector->moduleImplementsAlters as $alter) {
+      foreach ($this->moduleImplementsAlters as $alter) {
         $alter($moduleImplements, $hook);
       }
       foreach ($moduleImplements as $module => $v) {
@@ -224,6 +213,8 @@ protected static function calculateImplementations(
       }
     }
 
+    /** @var list<\Drupal\Core\Hook\HookOperation> $hookOrderOperations */
+    $hookOrderOperations = array_merge(...$this->orderAttributesByPhase);
     foreach ($hookOrderOperations as $hookOrderOperation) {
       static::applyOrderAttributeOperation(
         $implementationsByHook,
@@ -351,8 +342,8 @@ protected static function writeImplementationsToContainer(
    * @param array $module_filenames
    *   An associative array. Keys are the module names, values are relevant
    *   info yml file path.
-   * @param \Symfony\Component\DependencyInjection\ContainerBuilder|null $container
-   *   The container.
+   * @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
@@ -362,19 +353,16 @@ protected static function writeImplementationsToContainer(
    *   This method is only used by ModuleHandler.
    *
    * @todo Pass only $container when ModuleHandler::add() is removed
-   *   @see https://www.drupal.org/project/drupal/issues/3481778
+   * @see https://www.drupal.org/project/drupal/issues/3481778
    */
-  public static function collectAllHookImplementations(array $module_filenames, ?ContainerBuilder $container = NULL): static {
+  public static function collectAllHookImplementations(array $module_filenames, array $skipProceduralModules = []): static {
     $modules = array_map(static 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");
-      }
+      $skip_procedural = in_array($module, $skipProceduralModules);
       $collector->collectModuleHookImplementations(dirname($info['pathname']), $module, $module_preg, $skip_procedural);
     }
     return $collector;
-- 
GitLab