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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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/181] 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 From be86b5d5a7f968e250a7eb5b0cec7c2b6f8c0962 Mon Sep 17 00:00:00 2001 From: Andreas Hennings <andreas@dqxtech.net> Date: Fri, 7 Mar 2025 17:52:19 +0100 Subject: [PATCH 174/181] Rename some vars in collectAllHookImplementations(). --- core/lib/Drupal/Core/Hook/HookCollectorPass.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php index 02d78fe8be4d..98c7aaca76da 100644 --- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php +++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php @@ -339,7 +339,7 @@ protected static function writeImplementationsToContainer( /** * Collects all hook implementations. * - * @param array $module_filenames + * @param array $module_list * An associative array. Keys are the module names, values are relevant * info yml file path. * @param list<string> $skipProceduralModules @@ -355,13 +355,17 @@ protected static function writeImplementationsToContainer( * @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, array $skipProceduralModules = []): static { - $modules = array_map(static fn ($x) => preg_quote($x, '/'), array_keys($module_filenames)); + public static function collectAllHookImplementations(array $module_list, array $skipProceduralModules = []): static { + $modules = array_keys($module_list); // Longer modules first. - usort($modules, fn($a, $b) => strlen($b) - strlen($a)); - $module_preg = '/^(?<function>(?<module>' . implode('|', $modules) . ')_(?!preprocess_)(?!update_\d)(?<hook>[a-zA-Z0-9_\x80-\xff]+$))/'; + usort($modules, fn ($a, $b) => strlen($b) - strlen($a)); + $known_modules_pattern = implode('|', array_map( + static fn ($x) => preg_quote($x, '/'), + $modules, + )); + $module_preg = '/^(?<function>(?<module>' . $known_modules_pattern . ')_(?!preprocess_)(?!update_\d)(?<hook>[a-zA-Z0-9_\x80-\xff]+$))/'; $collector = new static(); - foreach ($module_filenames as $module => $info) { + foreach ($module_list as $module => $info) { $skip_procedural = in_array($module, $skipProceduralModules); $collector->collectModuleHookImplementations(dirname($info['pathname']), $module, $module_preg, $skip_procedural); } -- GitLab From 575f5b29360ccd7f151d48dc25e61b441e8aa46d Mon Sep 17 00:00:00 2001 From: Andreas Hennings <andreas@dqxtech.net> Date: Fri, 7 Mar 2025 18:19:00 +0100 Subject: [PATCH 175/181] Array type docs on ModuleHandler::__construct() - param $groupIncludes. --- core/lib/Drupal/Core/Extension/ModuleHandler.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index c9123822cb32..27e2d9c6b095 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php @@ -78,8 +78,9 @@ class ModuleHandler implements ModuleHandlerInterface { * The event dispatcher. * @param array $hookImplementationsMap * An array keyed by hook, classname, method and the value is the module. - * @param array $groupIncludes - * An array of .inc files to get helpers from. + * @param array<string, list<string>> $groupIncludes + * Lists of *.inc file paths that contain procedural implementations, keyed + * by hook name. * @param array<string, list<string>> $orderedExtraTypes * A multidimensional array of hooks that have been ordered and the * extra_types they have been ordered against. This is stored separately -- GitLab From 712ffe331618360d524113668e51a69febf26592 Mon Sep 17 00:00:00 2001 From: Andreas Hennings <andreas@dqxtech.net> Date: Tue, 4 Mar 2025 00:03:00 +0100 Subject: [PATCH 176/181] Really out of scope array docs. --- core/lib/Drupal/Core/Extension/ModuleHandler.php | 14 +++++++------- .../Core/Extension/ModuleHandlerInterface.php | 2 +- core/lib/Drupal/Core/Hook/HookCollectorPass.php | 14 +++++++++++--- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index 27e2d9c6b095..e514b3d8db06 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php @@ -61,7 +61,7 @@ class ModuleHandler implements ModuleHandlerInterface { /** * Hook and module keyed list of listeners. * - * @var array + * @var array<string, array<string, list<callable>>> */ protected array $invokeMap = []; @@ -70,13 +70,13 @@ class ModuleHandler implements ModuleHandlerInterface { * * @param string $root * The app root. - * @param array $module_list + * @param array<string, array{type: string, pathname: string, filename: string}> $module_list * An associative array whose keys are the names of installed modules and * whose values are Extension class parameters. This is normally the * %container.modules% parameter being set up by DrupalKernel. * @param \Symfony\Component\EventDispatcher\EventDispatcherInterface $eventDispatcher * The event dispatcher. - * @param array $hookImplementationsMap + * @param array<string, array<class-string, array<string, string>>> $hookImplementationsMap * An array keyed by hook, classname, method and the value is the module. * @param array<string, list<string>> $groupIncludes * Lists of *.inc file paths that contain procedural implementations, keyed @@ -488,12 +488,12 @@ public function alter($type, &$data, &$context1 = NULL, &$context2 = NULL) { /** * Reorder modules for alters. * - * @param array $modules + * @param list<string> $modules * A list of modules. * @param string $hook - * The hook being worked on, for example form_alter. + * The hook being worked on, for example 'form_alter'. * - * @return array + * @return list<string> * The list, potentially reordered and changed by * hook_module_implements_alter(). */ @@ -569,7 +569,7 @@ public function writeCache() { * @param string $hook * The name of the hook. * - * @return array + * @return array<string, list<callable>> * A list of event listeners implementing this hook. */ protected function getHookListeners(string $hook): array { diff --git a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php index aeded22ca926..b6aa7fde5e76 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandlerInterface.php @@ -217,7 +217,7 @@ public function hasImplementations(string $hook, $modules = NULL): bool; * * @param string $hook * The name of the hook to invoke. - * @param callable $callback + * @param callable(callable, string): mixed $callback * A callable that invokes a hook implementation. Such that * $callback is callable(callable, string): mixed. * Arguments: diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php index 98c7aaca76da..de4c75f55124 100644 --- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php +++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php @@ -33,9 +33,11 @@ class HookCollectorPass implements CompilerPassInterface { /** - * A list of include files. + * A map of include files by function name. * * (This is required only for BC.) + * + * @var array<string, string> */ protected array $includes = []; @@ -43,6 +45,8 @@ class HookCollectorPass implements CompilerPassInterface { * A list of functions implementing hook_module_implements_alter(). * * (This is required only for BC.) + * + * @var list<callable-string> */ protected array $moduleImplementsAlters = []; @@ -50,11 +54,15 @@ class HookCollectorPass implements CompilerPassInterface { * A list of functions implementing hook_hook_info(). * * (This is required only for BC.) + * + * @var list<callable-string> */ private array $hookInfo = []; /** - * A list of .inc files. + * Include files, keyed by the $group part of "/$module.$group.inc". + * + * @var array<string, list<string>> */ private array $groupIncludes = []; @@ -339,7 +347,7 @@ protected static function writeImplementationsToContainer( /** * Collects all hook implementations. * - * @param array $module_list + * @param array<string, array{pathname: string}> $module_list * An associative array. Keys are the module names, values are relevant * info yml file path. * @param list<string> $skipProceduralModules -- GitLab From 00ab443ff8d84246de86bbc1d8c5fd0a44508e98 Mon Sep 17 00:00:00 2001 From: Andreas Hennings <andreas@dqxtech.net> Date: Fri, 7 Mar 2025 17:20:34 +0100 Subject: [PATCH 177/181] Split HookCollector out of HookCollectorPass, step 1. --- core/lib/Drupal/Core/CoreServiceProvider.php | 4 +- .../Drupal/Core/Hook/HookCollectorPass.php | 22 ++-------- .../Core/Hook/HookCollectorPassTmpRename.php | 40 +++++++++++++++++++ .../Core/Hook/HookCollectorPassTest.php | 10 ++--- .../Tests/Core/GroupIncludesTestTrait.php | 2 +- .../Tests/Core/Hook/HookCollectorPassTest.php | 8 ++-- 6 files changed, 55 insertions(+), 31 deletions(-) create mode 100644 core/lib/Drupal/Core/Hook/HookCollectorPassTmpRename.php diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php index 106df846dbd7..67bddea9fdbf 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -10,7 +10,7 @@ use Drupal\Core\DependencyInjection\Compiler\CorsCompilerPass; use Drupal\Core\DependencyInjection\Compiler\DeprecatedServicePass; use Drupal\Core\DependencyInjection\Compiler\DevelopmentSettingsPass; -use Drupal\Core\Hook\HookCollectorPass; +use Drupal\Core\Hook\HookCollectorPassTmpRename; use Drupal\Core\DependencyInjection\Compiler\LoggerAwarePass; use Drupal\Core\DependencyInjection\Compiler\ModifyServiceDefinitionsPass; use Drupal\Core\DependencyInjection\Compiler\ProxyServicesPass; @@ -64,7 +64,7 @@ public function register(ContainerBuilder $container) { ->addTag('stream_wrapper', ['scheme' => 'private']); } - $container->addCompilerPass(new HookCollectorPass()); + $container->addCompilerPass(new HookCollectorPassTmpRename()); // Add the compiler pass that lets service providers modify existing // service definitions. This pass must come before all passes operating on // services so that later list-building passes are operating on the diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php index de4c75f55124..08c4522d16ad 100644 --- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php +++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php @@ -11,10 +11,9 @@ 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\ReOrderHook; use Drupal\Core\Hook\Attribute\StopProceduralHookScan; -use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; /** @@ -30,7 +29,7 @@ * Finally, a hook_implementations_map container parameter is added. This * contains a mapping from [hook,class,method] to the module name. */ -class HookCollectorPass implements CompilerPassInterface { +class HookCollectorPass { /** * A map of include files by function name. @@ -83,28 +82,13 @@ class HookCollectorPass implements CompilerPassInterface { */ protected array $removeHookAttributes = []; - /** - * {@inheritdoc} - */ - public function process(ContainerBuilder $container): void { - $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); - - $collector->writeToContainer($container); - } - /** * Writes collected definitions to the container builder. * * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container * Container builder. */ - protected function writeToContainer(ContainerBuilder $container): void { + public function writeToContainer(ContainerBuilder $container): void { $orderExtraTypes = $this->getOrderExtraTypes(); $container->register(ProceduralCall::class, ProceduralCall::class) diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPassTmpRename.php b/core/lib/Drupal/Core/Hook/HookCollectorPassTmpRename.php new file mode 100644 index 000000000000..aff7bb22cdcd --- /dev/null +++ b/core/lib/Drupal/Core/Hook/HookCollectorPassTmpRename.php @@ -0,0 +1,40 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Core\Hook; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Collects and registers hook implementations. + * + * A hook implementation is a class in a Drupal\modulename\Hook namespace + * where either the class itself or the methods have a #[Hook] attribute. + * These classes are automatically registered as autowired services. + * + * Services for procedural implementation of hooks are also registered + * using the ProceduralCall class. + * + * Finally, a hook_implementations_map container parameter is added. This + * contains a mapping from [hook,class,method] to the module name. + */ +class HookCollectorPassTmpRename implements CompilerPassInterface { + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container): void { + $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 = HookCollectorPass::collectAllHookImplementations($module_list, $skip_procedural_modules); + + $collector->writeToContainer($container); + } + +} diff --git a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php index 10765f3a084b..309b8ef42a7a 100644 --- a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php +++ b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php @@ -4,13 +4,13 @@ namespace Drupal\KernelTests\Core\Hook; -use Drupal\Core\Hook\HookCollectorPass; +use Drupal\Core\Hook\HookCollectorPassTmpRename; use Drupal\KernelTests\KernelTestBase; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; /** - * @coversDefaultClass \Drupal\Core\Hook\HookCollectorPass + * @coversDefaultClass \Drupal\Core\Hook\HookCollectorPassTmpRename * @group Hook */ class HookCollectorPassTest extends KernelTestBase { @@ -38,7 +38,7 @@ public function testSymlink(): void { ]; $container->setParameter('container.modules', $module_filenames); $container->setDefinition('module_handler', new Definition()); - (new HookCollectorPass())->process($container); + (new HookCollectorPassTmpRename())->process($container); $implementations = [ 'user_format_name_alter' => [ 'Drupal\user_hooks_test\Hook\UserHooksTest' => [ @@ -64,7 +64,7 @@ public function testOrdering(): void { include_once 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all1/src/Hook/ModuleHandlerTestAll1Hooks.php'; $container->setParameter('container.modules', $module_filenames); $container->setDefinition('module_handler', new Definition()); - (new HookCollectorPass())->process($container); + (new HookCollectorPassTmpRename())->process($container); $priorities = []; foreach ($container->findTaggedServiceIds('kernel.event_listener') as $tags) { foreach ($tags as $attributes) { @@ -95,7 +95,7 @@ public function testLegacyModuleImplementsAlter(): void { 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); + (new HookCollectorPassTmpRename())->process($container); // This test will also fail if the deprecation notice shows up. $this->assertFalse(isset($GLOBALS['ShouldNotRunLegacyModuleImplementsAlter'])); diff --git a/core/tests/Drupal/Tests/Core/GroupIncludesTestTrait.php b/core/tests/Drupal/Tests/Core/GroupIncludesTestTrait.php index 21157cdf1a05..b1ee243a8d02 100644 --- a/core/tests/Drupal/Tests/Core/GroupIncludesTestTrait.php +++ b/core/tests/Drupal/Tests/Core/GroupIncludesTestTrait.php @@ -7,7 +7,7 @@ use org\bovigo\vfs\vfsStream; /** - * @coversDefaultClass \Drupal\Core\Hook\HookCollectorPass + * @coversDefaultClass \Drupal\Core\Hook\HookCollectorPassTmpRename * @group Hook */ trait GroupIncludesTestTrait { diff --git a/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php index 2d6fba3b451f..07673b77c118 100644 --- a/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php +++ b/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php @@ -6,7 +6,7 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Core\Extension\ProceduralCall; -use Drupal\Core\Hook\HookCollectorPass; +use Drupal\Core\Hook\HookCollectorPassTmpRename; use Drupal\Tests\UnitTestCase; use Drupal\Tests\Core\GroupIncludesTestTrait; use org\bovigo\vfs\vfsStream; @@ -14,7 +14,7 @@ use Symfony\Component\DependencyInjection\Definition; /** - * @coversDefaultClass \Drupal\Core\Hook\HookCollectorPass + * @coversDefaultClass \Drupal\Core\Hook\HookCollectorPassTmpRename * @group Hook */ class HookCollectorPassTest extends UnitTestCase { @@ -65,7 +65,7 @@ function test_module_should_be_skipped(); $container = new ContainerBuilder(); $container->setParameter('container.modules', $module_filenames); $container->setDefinition('module_handler', new Definition()); - (new HookCollectorPass())->process($container); + (new HookCollectorPassTmpRename())->process($container); $this->assertSame($implementations, $container->getParameter('hook_implementations_map')); $this->assertSame($includes, $container->getDefinition(ProceduralCall::class)->getArguments()[0]); } @@ -79,7 +79,7 @@ public function testGroupIncludes(): void { $container = new ContainerBuilder(); $container->setParameter('container.modules', $module_filenames); $container->setDefinition('module_handler', new Definition()); - (new HookCollectorPass())->process($container); + (new HookCollectorPassTmpRename())->process($container); $argument = $container->getDefinition('module_handler')->getArgument('$groupIncludes'); $this->assertSame(self::GROUP_INCLUDES, $argument); } -- GitLab From d76497883ac5a25794b0896d9b75f72f2c498079 Mon Sep 17 00:00:00 2001 From: Andreas Hennings <andreas@dqxtech.net> Date: Fri, 7 Mar 2025 17:39:43 +0100 Subject: [PATCH 178/181] Split HookCollector out of HookCollectorPass, step 2. --- core/lib/Drupal/Core/Extension/ModuleHandler.php | 4 ++-- .../Core/Hook/{HookCollectorPass.php => HookCollector.php} | 2 +- core/lib/Drupal/Core/Hook/HookCollectorPassTmpRename.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename core/lib/Drupal/Core/Hook/{HookCollectorPass.php => HookCollector.php} (99%) diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index e514b3d8db06..8bed8e860cfe 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php @@ -6,7 +6,7 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Core\Extension\Exception\UnknownExtensionException; use Drupal\Core\Hook\Attribute\LegacyHook; -use Drupal\Core\Hook\HookCollectorPass; +use Drupal\Core\Hook\HookCollector; use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** @@ -206,7 +206,7 @@ protected function add($type, $name, $path) { $this->moduleList[$name] = new Extension($this->root, $type, $pathname, $filename); $this->resetImplementations(); $paths = [$name => ['pathname' => $pathname]]; - $hook_collector = HookCollectorPass::collectAllHookImplementations($paths); + $hook_collector = HookCollector::collectAllHookImplementations($paths); // A module freshly added will not be registered on the container yet. // ProceduralCall service does not yet know about it. // Note in HookCollectorPass: diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPass.php b/core/lib/Drupal/Core/Hook/HookCollector.php similarity index 99% rename from core/lib/Drupal/Core/Hook/HookCollectorPass.php rename to core/lib/Drupal/Core/Hook/HookCollector.php index 08c4522d16ad..421ccab6104d 100644 --- a/core/lib/Drupal/Core/Hook/HookCollectorPass.php +++ b/core/lib/Drupal/Core/Hook/HookCollector.php @@ -29,7 +29,7 @@ * Finally, a hook_implementations_map container parameter is added. This * contains a mapping from [hook,class,method] to the module name. */ -class HookCollectorPass { +class HookCollector { /** * A map of include files by function name. diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPassTmpRename.php b/core/lib/Drupal/Core/Hook/HookCollectorPassTmpRename.php index aff7bb22cdcd..b8b6b0485aa9 100644 --- a/core/lib/Drupal/Core/Hook/HookCollectorPassTmpRename.php +++ b/core/lib/Drupal/Core/Hook/HookCollectorPassTmpRename.php @@ -32,7 +32,7 @@ public function process(ContainerBuilder $container): void { array_keys($module_list), fn (string $module) => !empty($parameters["$module.hooks_converted"]), ); - $collector = HookCollectorPass::collectAllHookImplementations($module_list, $skip_procedural_modules); + $collector = HookCollector::collectAllHookImplementations($module_list, $skip_procedural_modules); $collector->writeToContainer($container); } -- GitLab From 99eb889f780d5ccc54fb91d1deddca0f2db386f4 Mon Sep 17 00:00:00 2001 From: Andreas Hennings <andreas@dqxtech.net> Date: Fri, 7 Mar 2025 17:40:06 +0100 Subject: [PATCH 179/181] Split HookCollector out of HookCollectorPass, step 3. --- core/lib/Drupal/Core/CoreServiceProvider.php | 4 ++-- ...ollectorPassTmpRename.php => HookCollectorPass.php} | 2 +- .../KernelTests/Core/Hook/HookCollectorPassTest.php | 10 +++++----- .../tests/Drupal/Tests/Core/GroupIncludesTestTrait.php | 2 +- .../Drupal/Tests/Core/Hook/HookCollectorPassTest.php | 8 ++++---- 5 files changed, 13 insertions(+), 13 deletions(-) rename core/lib/Drupal/Core/Hook/{HookCollectorPassTmpRename.php => HookCollectorPass.php} (94%) diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php index 67bddea9fdbf..106df846dbd7 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -10,7 +10,7 @@ use Drupal\Core\DependencyInjection\Compiler\CorsCompilerPass; use Drupal\Core\DependencyInjection\Compiler\DeprecatedServicePass; use Drupal\Core\DependencyInjection\Compiler\DevelopmentSettingsPass; -use Drupal\Core\Hook\HookCollectorPassTmpRename; +use Drupal\Core\Hook\HookCollectorPass; use Drupal\Core\DependencyInjection\Compiler\LoggerAwarePass; use Drupal\Core\DependencyInjection\Compiler\ModifyServiceDefinitionsPass; use Drupal\Core\DependencyInjection\Compiler\ProxyServicesPass; @@ -64,7 +64,7 @@ public function register(ContainerBuilder $container) { ->addTag('stream_wrapper', ['scheme' => 'private']); } - $container->addCompilerPass(new HookCollectorPassTmpRename()); + $container->addCompilerPass(new HookCollectorPass()); // Add the compiler pass that lets service providers modify existing // service definitions. This pass must come before all passes operating on // services so that later list-building passes are operating on the diff --git a/core/lib/Drupal/Core/Hook/HookCollectorPassTmpRename.php b/core/lib/Drupal/Core/Hook/HookCollectorPass.php similarity index 94% rename from core/lib/Drupal/Core/Hook/HookCollectorPassTmpRename.php rename to core/lib/Drupal/Core/Hook/HookCollectorPass.php index b8b6b0485aa9..2839dc110919 100644 --- a/core/lib/Drupal/Core/Hook/HookCollectorPassTmpRename.php +++ b/core/lib/Drupal/Core/Hook/HookCollectorPass.php @@ -20,7 +20,7 @@ * Finally, a hook_implementations_map container parameter is added. This * contains a mapping from [hook,class,method] to the module name. */ -class HookCollectorPassTmpRename implements CompilerPassInterface { +class HookCollectorPass implements CompilerPassInterface { /** * {@inheritdoc} diff --git a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php index 309b8ef42a7a..10765f3a084b 100644 --- a/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php +++ b/core/tests/Drupal/KernelTests/Core/Hook/HookCollectorPassTest.php @@ -4,13 +4,13 @@ namespace Drupal\KernelTests\Core\Hook; -use Drupal\Core\Hook\HookCollectorPassTmpRename; +use Drupal\Core\Hook\HookCollectorPass; use Drupal\KernelTests\KernelTestBase; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; /** - * @coversDefaultClass \Drupal\Core\Hook\HookCollectorPassTmpRename + * @coversDefaultClass \Drupal\Core\Hook\HookCollectorPass * @group Hook */ class HookCollectorPassTest extends KernelTestBase { @@ -38,7 +38,7 @@ public function testSymlink(): void { ]; $container->setParameter('container.modules', $module_filenames); $container->setDefinition('module_handler', new Definition()); - (new HookCollectorPassTmpRename())->process($container); + (new HookCollectorPass())->process($container); $implementations = [ 'user_format_name_alter' => [ 'Drupal\user_hooks_test\Hook\UserHooksTest' => [ @@ -64,7 +64,7 @@ public function testOrdering(): void { include_once 'core/tests/Drupal/Tests/Core/Extension/modules/module_handler_test_all1/src/Hook/ModuleHandlerTestAll1Hooks.php'; $container->setParameter('container.modules', $module_filenames); $container->setDefinition('module_handler', new Definition()); - (new HookCollectorPassTmpRename())->process($container); + (new HookCollectorPass())->process($container); $priorities = []; foreach ($container->findTaggedServiceIds('kernel.event_listener') as $tags) { foreach ($tags as $attributes) { @@ -95,7 +95,7 @@ public function testLegacyModuleImplementsAlter(): void { 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 HookCollectorPassTmpRename())->process($container); + (new HookCollectorPass())->process($container); // This test will also fail if the deprecation notice shows up. $this->assertFalse(isset($GLOBALS['ShouldNotRunLegacyModuleImplementsAlter'])); diff --git a/core/tests/Drupal/Tests/Core/GroupIncludesTestTrait.php b/core/tests/Drupal/Tests/Core/GroupIncludesTestTrait.php index b1ee243a8d02..21157cdf1a05 100644 --- a/core/tests/Drupal/Tests/Core/GroupIncludesTestTrait.php +++ b/core/tests/Drupal/Tests/Core/GroupIncludesTestTrait.php @@ -7,7 +7,7 @@ use org\bovigo\vfs\vfsStream; /** - * @coversDefaultClass \Drupal\Core\Hook\HookCollectorPassTmpRename + * @coversDefaultClass \Drupal\Core\Hook\HookCollectorPass * @group Hook */ trait GroupIncludesTestTrait { diff --git a/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php index 07673b77c118..2d6fba3b451f 100644 --- a/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php +++ b/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php @@ -6,7 +6,7 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Core\Extension\ProceduralCall; -use Drupal\Core\Hook\HookCollectorPassTmpRename; +use Drupal\Core\Hook\HookCollectorPass; use Drupal\Tests\UnitTestCase; use Drupal\Tests\Core\GroupIncludesTestTrait; use org\bovigo\vfs\vfsStream; @@ -14,7 +14,7 @@ use Symfony\Component\DependencyInjection\Definition; /** - * @coversDefaultClass \Drupal\Core\Hook\HookCollectorPassTmpRename + * @coversDefaultClass \Drupal\Core\Hook\HookCollectorPass * @group Hook */ class HookCollectorPassTest extends UnitTestCase { @@ -65,7 +65,7 @@ function test_module_should_be_skipped(); $container = new ContainerBuilder(); $container->setParameter('container.modules', $module_filenames); $container->setDefinition('module_handler', new Definition()); - (new HookCollectorPassTmpRename())->process($container); + (new HookCollectorPass())->process($container); $this->assertSame($implementations, $container->getParameter('hook_implementations_map')); $this->assertSame($includes, $container->getDefinition(ProceduralCall::class)->getArguments()[0]); } @@ -79,7 +79,7 @@ public function testGroupIncludes(): void { $container = new ContainerBuilder(); $container->setParameter('container.modules', $module_filenames); $container->setDefinition('module_handler', new Definition()); - (new HookCollectorPassTmpRename())->process($container); + (new HookCollectorPass())->process($container); $argument = $container->getDefinition('module_handler')->getArgument('$groupIncludes'); $this->assertSame(self::GROUP_INCLUDES, $argument); } -- GitLab From 94ac62bad24f1c64b21890c29a5ffedda05e95c1 Mon Sep 17 00:00:00 2001 From: Andreas Hennings <andreas@dqxtech.net> Date: Fri, 7 Mar 2025 18:53:36 +0100 Subject: [PATCH 180/181] Fix test covers docs after rename. --- core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php b/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php index 2d6fba3b451f..2bd30610cb19 100644 --- a/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php +++ b/core/tests/Drupal/Tests/Core/Hook/HookCollectorPassTest.php @@ -22,8 +22,8 @@ class HookCollectorPassTest extends UnitTestCase { use GroupIncludesTestTrait; /** - * @covers ::collectAllHookImplementations - * @covers ::filterIterator + * @covers \Drupal\Core\Hook\HookCollector::collectAllHookImplementations + * @covers \Drupal\Core\Hook\HookCollector::filterIterator */ public function testCollectAllHookImplementations(): void { vfsStream::setup('drupal_root'); @@ -72,7 +72,7 @@ function test_module_should_be_skipped(); /** * @covers ::process - * @covers ::collectModuleHookImplementations + * @covers \Drupal\Core\Hook\HookCollector::collectModuleHookImplementations */ public function testGroupIncludes(): void { $module_filenames = self::setupGroupIncludes(); -- GitLab From 16351c81cfac8737a3c9278b89a04e9206c8d4ce Mon Sep 17 00:00:00 2001 From: Andreas Hennings <andreas@dqxtech.net> Date: Fri, 7 Mar 2025 18:27:36 +0100 Subject: [PATCH 181/181] Let HookCollector store its own list of module names. --- .../Drupal/Core/Extension/ModuleHandler.php | 2 +- core/lib/Drupal/Core/Hook/HookCollector.php | 20 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/core/lib/Drupal/Core/Extension/ModuleHandler.php b/core/lib/Drupal/Core/Extension/ModuleHandler.php index 8bed8e860cfe..f476ae2af809 100644 --- a/core/lib/Drupal/Core/Extension/ModuleHandler.php +++ b/core/lib/Drupal/Core/Extension/ModuleHandler.php @@ -214,7 +214,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($paths) as $hook => $moduleImplements) { + foreach ($hook_collector->getImplementations() 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/HookCollector.php b/core/lib/Drupal/Core/Hook/HookCollector.php index 421ccab6104d..2008b0858287 100644 --- a/core/lib/Drupal/Core/Hook/HookCollector.php +++ b/core/lib/Drupal/Core/Hook/HookCollector.php @@ -82,6 +82,16 @@ class HookCollector { */ protected array $removeHookAttributes = []; + /** + * Constructor. Should not be called directly. + * + * @param list<string> $modules + * Names of installed modules. + */ + protected function __construct( + protected readonly array $modules, + ) {} + /** * Writes collected definitions to the container builder. * @@ -356,7 +366,7 @@ public static function collectAllHookImplementations(array $module_list, array $ $modules, )); $module_preg = '/^(?<function>(?<module>' . $known_modules_pattern . ')_(?!preprocess_)(?!update_\d)(?<hook>[a-zA-Z0-9_\x80-\xff]+$))/'; - $collector = new static(); + $collector = new static($modules); foreach ($module_list as $module => $info) { $skip_procedural = in_array($module, $skipProceduralModules); $collector->collectModuleHookImplementations(dirname($info['pathname']), $module, $module_preg, $skip_procedural); @@ -525,9 +535,6 @@ 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. * @@ -536,15 +543,14 @@ public function loadAllIncludes(): void { * * @internal */ - public function getImplementations(array $paths): array { + public function getImplementations(): array { $implementationsByHook = $this->getFilteredImplementations(); // List of modules implementing hooks with the implementation details. $implementations = []; - $modules = array_keys($paths); foreach ($implementationsByHook as $hook => $hookImplementations) { - foreach ($modules as $module) { + foreach ($this->modules as $module) { foreach (array_keys($hookImplementations, $module, TRUE) as $identifier) { [$class, $method] = explode('::', $identifier); $implementations[$hook][$module][$class][$method] = $method; -- GitLab