Procedural-Only Hooks
When to Use
You are converting a module and need the list of hooks that cannot become
#[Hook]methods, where each one lives, and what replaceshook_requirements().
Items
Install and update hooks
Called by core/lib/Drupal/Core/Extension/ModuleInstaller.php with function_exists("{$module}_{$hook}"). The hook collector never touches them, and #[Hook] on any of these names throws a LogicException at container build (HookCollectorPass::checkForProceduralOnlyHooks()).
| Hook | Lives in |
|---|---|
hook_install(), hook_uninstall() |
<module>.install |
hook_schema() |
<module>.install |
hook_update_N(), hook_update_last_removed(), hook_update_dependencies() |
<module>.install |
hook_post_update_NAME(), hook_removed_post_updates() |
<module>.post_update.php |
hook_install_tasks(), hook_install_tasks_alter() |
<profile>.profile |
Gotchas:
- These stay procedural in core's own modules too. The question of deprecating them is open in plan issue 3481555, which is at "Needs review" and is not policy.
- hook_install(), hook_uninstall() receive $is_syncing (8.9.0, change record 3098920).
Legacy meta hooks
| Hook | Lives in | Status |
|---|---|---|
hook_hook_info() |
<module>.module |
Include groups ($module.$group.inc) deprecated 11.2.0, removed 12.0.0 (change record 3489765). The hook itself is intentionally not deprecated; support ends in 12.0.0 (core/lib/Drupal/Core/Extension/module.api.php docblock) |
hook_module_implements_alter() |
<module>.module |
Without #[LegacyModuleImplementsAlter]: deprecated 11.2.0, removed 12.0.0 (change record 3496788). See Hook Ordering |
Gotchas:
- Both are still collected and executed in 11.4.5. hook_hook_info() runs at container build to compute group includes; hook_module_implements_alter() is invoked through the normal alter machinery in ModuleHandler::reOrderModulesForAlter().
- A module with hook_hook_info() must not set skip_procedural_hook_scan; the hook would never be found (change record 3490771).
hook_requirements() and its replacements
hook_requirements() is deprecated 11.2.0, removed 12.0.0 (change record 3512364). Its three phases split into three mechanisms, all available since 11.2.0 (change record 3549685):
| Phase | Replacement | OOP? | Core example |
|---|---|---|---|
install |
Class implementing InstallRequirementsInterface in src/Install/Requirements/<Module>Requirements.php, public static function getRequirements(): array |
Static class, not a hook | core/modules/system/src/Install/Requirements/SystemRequirements.php, core/modules/media/..., core/modules/workspaces/..., core/modules/package_manager/..., core/modules/pgsql/... |
runtime |
hook_runtime_requirements() + hook_runtime_requirements_alter() |
Yes, #[Hook('runtime_requirements')] |
core/modules/node/src/Hook/NodeRequirements.php |
update |
hook_update_requirements() + hook_update_requirements_alter() |
Yes, #[Hook('update_requirements')] |
core/modules/system/src/Hook/SystemRequirementsHooks.php |
namespace Drupal\my_module\Install\Requirements;
use Drupal\Core\Extension\InstallRequirementsInterface;
use Drupal\Core\Extension\Requirement\RequirementSeverity;
class MyModuleRequirements implements InstallRequirementsInterface {
public static function getRequirements(): array {
return ['my_module_ext' => [
'title' => t('Required PHP extension'),
'severity' => extension_loaded('intl') ? RequirementSeverity::OK : RequirementSeverity::Error,
]];
}
}
core/includes/install.inc (install_check_class_requirements()) scans src/Install/Requirements/*.php, requires each file, and calls getRequirements() on any class implementing the interface. The class runs before the module's container exists, so it has no injection.
Severity is the RequirementSeverity enum (Info, OK, Warning, Error). Integer constants are deprecated 11.2.0 and the enum is required in 12.0.0 (change record 3410939).
Gotchas:
- Two separate deprecations exist. The hook itself: 11.2.0 → 12.0.0. Running a procedural hook_requirements() without #[LegacyRequirementsHook]: 11.3.0 → 13.0.0 (change record 3549685, HookCollectorPass::addProceduralImplementation()).
- #[LegacyRequirementsHook] stops the procedural function on 11.3.0+ and silences the deprecation. 11.2 does not know the attribute and invokes both the legacy and the new hooks. Use it only while you still support core older than 11.2.
- #[Hook('requirements')] is on the deny list; the container throws.
Common Mistakes
- Moving
hook_schema()orhook_update_N()into a hook class → container build fails with "does not support attributes and must remain procedural." - Keeping
hook_requirements()with a$phaseswitch after your minimum is 11.2 → a deprecation notice at every container rebuild since 11.3.0. Split it. - Injecting services into the
InstallRequirementsInterfaceclass → it is instantiated by a static call during install; nothing is injected. - Returning
REQUIREMENT_ERRORintegers → deprecated since 11.2.0. ReturnRequirementSeverity::Error.
See Also
- ← Hook Ordering | Theme Hooks (OOP) →
- Reference:
core/lib/Drupal/Core/Extension/ModuleInstaller.php,core/lib/Drupal/Core/Extension/InstallRequirementsInterface.php,core/lib/Drupal/Core/Extension/Requirement/RequirementSeverity.php,core/lib/Drupal/Core/Extension/module.api.php,core/includes/install.inc - Change records: https://www.drupal.org/node/3512364, https://www.drupal.org/node/3549685, https://www.drupal.org/node/3410939, https://www.drupal.org/node/3489765, https://www.drupal.org/node/3496788