DEV Community

david duymelinck
david duymelinck

Posted on

New Drupal Hook attribute

In the new version announcement something has caught my eye. From the title you know it is the Hook attribute.
For the people who are not familiar with php attributes, I wrote a post about it a while ago.

The way you needed to add hooks was an eyesore for me since Drupal 8 moved to a object-oriented way of structuring the code.
Using the module name to prefix the functions and using the .module file to add all the functions had a very spaghetti code feel for me.

And now they almost fixed it. Almost because there are a bunch of hooks that are still procedural. The plan is to remove the procedural hooks in Drupal 12, so in the next minor Drupal versions we will see those hooks disappearing.

What are the benefits?

Instead of adding functions to the .module file, the hooks are in the src directory of the module.
I suggest to use a Hooks subdirectory for easier identification. Or add the Hooks suffix to the class name.

Because it is an attribute you can bind multiple hooks to the same method.

// module.module

function module_comment_insert(CommentInterface $comment) {
   module_comment_manipulation($comment);
}

function module_comment_update(CommentInterface $comment) {
   module_comment_manipulation($comment);
}

function module_comment_manipulation(CommentInterface $comment) {
  // do something
}

// with Hook attribute 
class CommentHooks {

  #[Hook('comment_insert')]
  #[Hook('comment_update')]
  public function commentInsertOrUpdate(CommentInterface $comment) {
    // do something
  }
}
Enter fullscreen mode Exit fullscreen mode

For the people that maintain modules for Drupal versions before 11.1 there is an extra attribute, LegacyHook. This allows you to move the code of the hook to the class with hook attribute. And the old versions of Drupal will execute the function in the .module file, but the newer versions will only execute the class method.

// module.module
#[LegacyHook]
function module_comment_insert(CommentInterface $comment) {
   new CommentHooks()->commentInsertOrUpdate($comment);
}

#[LegacyHook]
function module_comment_update(CommentInterface $comment) {
   new CommentHooks()->commentInsertOrUpdate($comment);
}
Enter fullscreen mode Exit fullscreen mode

How to use the Hook attribute

As you can see from the previous code examples the attribute is added to the method.

But you can also add the method to the class.

#[Hook('comment_insert')]
#[Hook('comment_update')]
class CommentManipulationHook {
  public function __invoke(CommentInterface $comment) {
    // do something
  }
}
Enter fullscreen mode Exit fullscreen mode

As I show in the example I suggest to make the class name more descriptive. And use the suffix Hook instead of Hooks.

You can add the Hook attributes to the class and add the method as the second parameter. I don't recommend this, In that case it is cleaner to add the attribute to the method.

There is a third Hook parameter, module. And this allows you to execute a hook class from another module. For example #hook('comment_insert', 'commentInsert', 'my_comment_module').
I have been thinking about a use case for this, but i couldn't find any.
If you know one, let me know.

Conclusion

I love to see Drupal code moving in the right direction.

The one thing that bothered me is that the hooks are magic constants. But the plan is to all the hooks attributes with as base class the Hook attribute. So instead of #[Hook('comment_insert')] it will be #[CommentInsert].
Another way they could do it is by using enums, grouped by module.

enum CommentHooks {
  case Insert;
  case Update;
}
Enter fullscreen mode Exit fullscreen mode

The information in this post is based on the documentation and the quick look i had of the implementation. When I have tested the feature there will be updates to post or an additional post.

Top comments (2)

Collapse
 
oskar_calvo_1615a9b3b293f profile image
oskar calvo

Moving hooks to classes is not something entirely new. It’s true that it has been available in core since version 11.1, but the same could be done since version 9.35 with the Hux module. I’ve been using that module since then, and honestly, it’s fantastic.

For me, the most important advantages of moving hooks to OOP are:

  • Clarity in the code: As mentioned in the article, having well-named classes is crucial, and it’s especially important to avoid creating generic classes that group different hooks together. Doing so would simply transfer the issues of the *.module file to a class.

  • Grouping code by functionality: This is particularly valuable for custom code. With the way we can now write our code, we can consider following the Vertical Slice Architecture (milanjovanovic.tech/blog/vertical-...). For example, if we are using pseudo fields, we can have a class containing the necessary hooks for pseudo fields. If we have a view mode with a preprocess, a field formatter, etc., all of this could be grouped into a class. This way, if in the future we need to remove that view mode because it’s no longer used, it would be as simple as deleting the class, leaving no leftover code anywhere else.

In summary, we’re going to see code that is much better structured, more organized, clearer, and cleaner.

Collapse
 
xwero profile image
david duymelinck

You are correct, It was possible before, but for some reason I can't remember at the moment, I found it too much of a hassle.

Now the plans are to remove the hook functions, they will give it proper attention and it will be a lot smoother to work with.