- Create a class in the Drupal\modulename\Hook namespace (or subdirectory). This will be automatically registered as an autowired service.
- Use the new
Drupal\Core\Hook\Attribute\Hookattribute either on methods or on the class. If it is on the class and the class doesn't have an__invokemethod then amethodargument is required.
The method implementing the hook has the same signature as the procedural counterpart.
A very simple example:
function node_user_cancel($edit, UserInterface $account, $method) {
// DO STUFF
}
after
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\user\UserInterface;
class NodeHooks {
#[Hook('user_cancel')]
public function userCancel($edit, UserInterface $account, $method) {
// DO STUFF
}
}
While classic hook implementations relied on a magic function name, now the class and method can have any name.
A method can have multiple Hook attributes if it implements multiple hooks. For example, node_comment_insert and node_comment_update have the exact same implementation and so they could become
#[Hook('comment_insert')]
#[Hook('comment_update')]
public function commentInsertOrUpdate(CommentInterface $comment) {
Also, a module can implement almost all hooks multiple times as documented on the Hook attribute itself.
Deprecations
The following methods on ModuleHandler have been deprecated, without replacement. These are removed in Drupal 12.
- ModuleHandler::getHookInfo()
- ModuleHandler::writeCache()
Backwards-compatible Hook implementation for Drupal versions from 10.1 to 11.0
Contrib modules which want to adopt this new approach to hooks and continue to support Drupal 10.1 and 11.0 are recommended to implement a new hook class, manually register that class as a service, and add a shim procedural implementation:
node.module
use Drupal\Core\Hook\Attribute\LegacyHook;
use Drupal\node\Hook\NodeHooks;
// @phpstan-ignore-next-line
#[LegacyHook]
function node_user_cancel($edit, UserInterface $account, $method) {
// This only needs to be returned if the hook previously had a return.
return \Drupal::service(NodeHooks::class)->userCancel($edit, $account, $method);
}
node.services.yml
services:
Drupal\node\Hook\NodeHooks:
class: Drupal\node\Hook\NodeHooks
autowire: true
src/Hook/NodeHooks.php
namespace Drupal\node\Hook;
use Drupal\Core\Hook\Attribute\Hook;
use Drupal\user\UserInterface;
class NodeHooks {
#[Hook('user_cancel')]
public function userCancel($edit, UserInterface $account, $method) {
// User cancellation processing.
}
}
With this approach, Drupal versions prior to this change (from 10.1 to 11.0) will call node_user_cancel, which in turn will call the new hook, and the LegacyHook attribute is ignored. Versions of Drupal from 11.1 will not call node_user_cancel because it is marked with the #[LegacyHook] attribute; instead, the new implementation will be called directly. This approach only supports Drupal 10.1 and later because in 10.1 aliases were added to core services for autowiring. The LegacyHook attribute class is not required for this to work (PHP ignores it), but for certain tools it is still better to include it. There is a Rector rule for making all these changes.
Since the attribute does not exist in all versions of Drupal it is recommended that you add a local phpstan ignore rule for each #[LegacyHook] attribute.
Once a contrib module converts all hooks to be OOP a "hooks_converted: true" parameter can be set in the container configuration to improve performance.
In a later Drupal version (Drupal 13 at the earliest), we expect that support for procedural hooks will be removed, at which time these services and the LegacyHook shims will need to be removed as well.
Breaking changes
Conditionally defined hook implementations are not supported.
if ($foo) {
function foo_hook() {
}
}
Update: if it is really badly necessary to dynamically define hooks, this issue comment explains how to do it.
Also, any classes extending ModuleHandler are broken. As the class has been rewritten from ground up, any classes relying on the internals of it will not work and a code review is forced by the changed constructor. Once again, such code should be extremely rare. Consider rewriting it as a service decorator.
Update: Since hook_module_implements_alter is called during build time, it does not support service calls. Also, dynamic ordering in general is extremely unlikely to be supported in the future.
Hooks that have had support added in follow up issues
Note that the following list is capturing the situation when this document was created. As the conversion of some hooks from this list is an ongoing process, you can check updates in the Follow-up issues converting hooks to OOP section, below.
Hooks called by ModuleInstaller
- hook_cache_flush()
- hook_module_preinstall()
- hook_module_preuninstall()
- hook_modules_installed()
- hook_modules_uninstalled()
These can be converted in modules.
- hook_theme_suggestion_HOOK()
- hook_theme_suggestions_HOOK_alter()
Hooks that remain procedural
Legacy meta hooks
- hook_hook_info()
- hook_module_implements_alter()
Install/Update hooks
- hook_install()
- hook_install_tasks()
- hook_install_tasks_alter()
- hook_post_update_NAME()
- hook_removed_post_updates()
- hook_requirements()
- hook_schema()
- hook_uninstall()
- hook_update_dependencies()
- hook_update_last_removed()
- hook_update_N()
All of these are being looked at but for now they remain procedural.
Theme hooks
- hook_preprocess_HOOK()
- hooks implemented BY themes
- Alter hooks implemented BY themes
- Hooks implemented by modules on BEHALF of a theme
Uppercase HOOK means a theme hook. (This is not at all confusing.) hook_theme() is INCLUDED in this change, it is not a theme hook. The registry explicitly calls moduleHandler:
$result = $this->moduleHandler->invoke($name, 'theme', $args);
Future Plans
While currently there's only a Hook attribute, we expect eventually every core hook will have a subclass of Hook. This is where the hook doxygen will live instead of an api.php file and it'll also allow dropping the name from the attribute, for eg #[CommentInsert].
The base #[Hook] attribute will always work, the ability to fire and implement a hook without any additional code is considered a very important property of procedural hooks and we strive to maintain it.
This is just a plan and subject to change.
Follow-up issues converting hooks to OOP
This is section is updated dynamically, as new hooks are converted to OOP, compared to the "Hooks that have had support added in follow up issues" list, that captured the situation as of the date this change record was created.
Drupal 11.2
- #3490851: Added hook_runtime_requirements() and hook_runtime_requirements_alter()
- #3490842: Create hook_update_requirements() and hook_update_requirements_alter()
- #3492429: There is a new InstallRequirementsInterface to provide install time requirements.
- #3496491: Preprocess functions in modules now support object-oriented implementations
- #3504125: template_preprocess_HOOK are defined as callbacks in the theme hook
- #3489765: Includes for hook_hook_info implementations have been deprecated.
- #3496788: hook_module_implements_alter requires the #[LegacyModuleImplementsAlter] attribute
- #3496786: Hook implementations can now be removed with a #[RemoveHook] attribute.
- #3497308: Reorder hook implementations in other modules with the #[ReorderHook] attribute
- #3493962: Hook implementations can be ordered with an order parameter
Comments
FYI for anybody on 10.3.x:
FYI for anybody on 10.3.x: https://www.drupal.org/project/drupal/issues/3482464. At time of writing, these Hook classes do not exist. Should only affect phpstan, but there's an MR in there to add them to 10.3.
Is there any additional
Is there any additional information about proper dependency injection practice for OOP hook implementation?
This has not been settled yet
Discussion and questions are welcome here: #3493453: [meta] Standardize and clean up hook classes in core
Another backwards-compatible Hook implementation for Drupal...
...which doesn't need to add anything in the mymodule.services.yml and is even 8.x / 9.x / 10.0 compatible (if you don't use the
LegacyHookattribute on those versions -Hook&LegacyHookcame in core on 10.3.x version).mymodule.module
Strongly recommend not doing this
The moment services are injected this will break.
I would recommend removing this comment please.
The moment services are
Not sure about what you mean saying this. I am using this for ages (from Drupal 8 to 10 - it also works on 11 but I don't need it anymore) even with classes implementing dependencies injection.
If the class is a service then use the service directly, else use the class_resolver.
We're hereby talking about legacy code that we are going to drop soon or later anyway, so what's the point of declaring a service just to autowire the class when it doesn't need to be a service and will be autowired on d11 without that service declaration? To me it's just more code to drop
I agree
I've also been using this approach for a long time, initially inspired from the content_moderation core module. I think it's better to provide arguments than asking someone to delete their comment, it's healthier and more productive.
About hook_update_N() - there
About
hook_update_N()- there is a proposal to rework them using a totally new approach: #3167625: Deprecate/replace hook_update_N() in favor of an object-oriented approach similar to Laravel migrations- deleted comment -
- deleted comment -
GitLabCI PHPStan - previous major
PHPStan has been added to GitLabCI for previous major. This is causing both
[Hook]and[LegacyHook]to be flagged as unknown attributes.I tried like this:
But it resulted in a warning about an empty line.
What actually worked was:
I needed to add this to both the .module file and the Hook class.
I have a concern that this will cause actual errors in the hook attributes to be missed, particularly when attributes for specific hooks are implemented in future.
Could [Hook] and [LegacyHook] be implemented in 10.6?
The need to add
@phpstan-ignore-next-lineso hook attributes won't trigger PHPStan warnings seems a bit ugly, and has potential to cause genuine errors to be missed.If Drupal 10.6 were to define the attributes, even if they did nothing, it would prevent the PHPStan warning for previous major, and allow these lines to be removed.
I don't know enough about PHP attributes to know if this would be possible, but if PHPStan for previous major could warn if a
[Hook]was implemented without a[LegacyHook]counterpart, that would be really helpful.We can't define the hook attributes
We now have parameters for Hook ordering. If we have a stub for the Hook attribute and there is a module that uses:
#[Hook('hook', order: OrderAfter)] and OrderAfter doesn't exist then there is a fatal error.
However if in addition Hook does not exist then nothing happens.
We could look into adding LegacyHook again since I can't see that getting parameters.
Change record for order parameter
Here's the related change record: https://www.drupal.org/node/3493962
http://www.DROWL.de || Professionelle Drupal Lösungen aus Ostwestfalen-Lippe (OWL)
http://www.webks.de || webks: websolutions kept simple - Webbasierte Lösungen die einfach überzeugen!
http://www.drupal-theming.com || Individuelle Responsive Themes
This documentation should be updated to state this
Thank you for the explanation of why Hook and LegacyHook classes have been removed.
Regarding that, don't you think we should update this page ?
OR
To allow contrib modules to be compatible with both Drupal 10 and 11
Example for hook_ENTITY_TYPE_presave
Would be great to also document hooks that use placeholders, like hook_ENTITY_TYPE_presave.
I guess for "node" it will now be:
But it's kind of special and should be documented.
http://www.DROWL.de || Professionelle Drupal Lösungen aus Ostwestfalen-Lippe (OWL)
http://www.webks.de || webks: websolutions kept simple - Webbasierte Lösungen die einfach überzeugen!
http://www.drupal-theming.com || Individuelle Responsive Themes
I think your comment here can help clarify that
I'm not sure I'd consider that special case, it is:
#[Hook('ENTITY_TYPE_presave')]if your entity isnodethen it would be#[Hook('node_presave')]which is no different than procedural.my_module_ENTITY_TYPE_presave would become my_module_node_presave
Does that help?
Yes, maybe my thoughts were
Yes, maybe my thoughts were just too complicated, I thought maybe others could also ask themselves if and how these replacements work for OOP hooks. So I think an example wouldn't hurt, but I'm also fine with not adding that.
http://www.DROWL.de || Professionelle Drupal Lösungen aus Ostwestfalen-Lippe (OWL)
http://www.webks.de || webks: websolutions kept simple - Webbasierte Lösungen die einfach überzeugen!
http://www.drupal-theming.com || Individuelle Responsive Themes
I agree we need to document better
I just think we can document this in a better location. The change record is meant to inform about the new features not be an evolving source of documentation. L
Maybe here
https://www.drupal.org/docs/develop/creating-modules/understanding-hooks
Or here
https://api.drupal.org/api/drupal/core%21core.api.php/group/hooks/11.x
LegacyHook attribute clarification
I am a bit confused by this line.
What exactly is the LegacyHook attribute class not required for? I read that paragraph a few times and I wasn't sure. Especially I just want to make sure that this does not contradict the statement a few sentences prior:
which as I understand does a great job at explaining the function of the attribute.
For context (and hopefully to help clarify the above with an example), I am currently working on making this transition and working to support backwards compatibility. I have a legacy hook_module_implements_alter() implementation. My understanding is that this hook remains completely procedural and therefore should not be marked with the LegacyHook attribute, while any hooks being implemented in my hook class should.
The LegacyHook attribute is
The LegacyHook attribute is not required for the 10.1 bc layer to work. It is only required to prevent 11.1 from executing the hook twice.
hook_module_implements_alter has replacements in 11.2 see https://www.drupal.org/node/3496788 and the 3 or 4 related change records.
There is a LegacyModuleImplementsAlter to assist bc in the same way as you replace ordering with the parameters.
Be aware though that bc hmia can be very complex.
Feel free to reach out on slack in the contribute channel if you have any questions.