Hook soup

💬 6

Of late, I seem to keep stumbling upon Drupal hooks that I've never heard of before. For example, I was just reading a blog post about what you can't modify in a _preprocess() function, when I saw mention of hook_theme_registry_alter(). What a mouthful. I ain't seen that one 'til now. Is it just me, or are new hooks popping up every second day in Drupal land? This got me wondering: exactly how many hooks are there in Drupal core right now? And by how much has this number changed over the past few Drupal versions? Since this information is conveniently available in the function lists on api.drupal.org, I decided to find out for myself. I counted the number of documented hook_foo() functions for Drupal core versions 4.7, 5, 6 and 7 (HEAD), and this is what I came up with (in pretty graph form):

Drupal hooks by core version
Drupal hooks by core version

And those numbers again (in plain text form):

Aaaagggghhhh!!! Talk about an explosion — what we've got on our hands is nothing less than hook soup. The rate of growth of Drupal hooks is out of control. And that's not counting themable functions (and templates) and template preprocessor functions, which are the other "magically called" functions whose mechanics developers need to understand. And as for hooks defined by contrib modules — even were we only counting the "big players", such as Views — well, let's not even go there; it's really too massive to contemplate.

In fairness, there are a number of good reasons why the amount of hooks has gone up so dramatically in Drupal 7:

Nevertheless, despite all these good reasons, the number of core hooks in HEAD right now is surely cause for concern. More hooks means a higher learning curve for people new to Drupal, and a lot of time wasted in looking up API references even for experienced developers. More hooks also means a bigger core codebase, which goes against our philosophy of striving to keep core lean, mean and super-small.

In order to get a better understanding of why D7 core has so many hooks, I decided to do a breakdown of the hooks based on their type. I came up with the "types" more-or-less arbitrarily, based on the naming conventions of the hooks, and also based on the purpose and the input/output format of each hook. The full list of hooks and types can be found further down. Here's the summary (in pretty graph form):

Hook breakdown by type
Hook breakdown by type

And those numbers again (in plain text form):

Type No. of hooks
misc action 44
info 30
alter 27
delete 20
insert 13
load 12
update 10
validate 6
form 4
misc combo 4
prepare 4
view 4
presave 3
check 2

As you can see, most of the hooks in core are "misc action" hooks, i.e. they allow modules to execute arbitrary (or not-so-arbitrary) code in response to some sort of action, and that action isn't covered by the other hook types that I used for classification. For the most part, the misc action hooks all serve an important purpose; however, we should be taking a good look at them, and seeing if we really need a hook for that many different events. DX is a balancing act between flexibility-slash-extensibility, and flexibility-slash-extensibility overload. Drupal has a tendency to lean towards the latter, if left unchecked. Also prominent in core are the "info" and "alter" hooks which, whether they end in the respective _info or _alter suffixes or not, return (for info) or modify (for alter) a more-or-less non-dynamic structured array of definitions. The DX balancing act applies to these hooks just as strongly: do we really need to allow developers to define and to change that many structured arrays, or are some of those hooks never likely to be implemented outside of core?

I leave further discussion on this topic to the rest of the community. This article is really just to present the numbers. If you haven't seen enough numbers or lists yet, you can find some more of them below. Otherwise, glad I could inform you.

Hooks in Drupal 4.7 core

  1. hook_access
  2. hook_auth
  3. hook_block
  4. hook_comment
  5. hook_cron
  6. hook_db_rewrite_sql
  7. hook_delete
  8. hook_elements
  9. hook_exit
  10. hook_file_download
  11. hook_filter
  12. hook_filter_tips
  13. hook_footer
  14. hook_form
  15. hook_form_alter
  16. hook_help
  17. hook_info
  18. hook_init
  19. hook_insert
  20. hook_install
  21. hook_link
  22. hook_load
  23. hook_menu
  24. hook_nodeapi
  25. hook_node_grants
  26. hook_node_info
  27. hook_perm
  28. hook_ping
  29. hook_prepare
  30. hook_search
  31. hook_search_preprocess
  32. hook_settings
  33. hook_submit
  34. hook_taxonomy
  35. hook_update
  36. hook_update_index
  37. hook_update_N
  38. hook_user
  39. hook_validate
  40. hook_view
  41. hook_xmlrpc

Hooks in Drupal 5 core

  1. hook_access
  2. hook_auth
  3. hook_block
  4. hook_comment
  5. hook_cron
  6. hook_db_rewrite_sql
  7. hook_delete
  8. hook_disable
  9. hook_elements
  10. hook_enable
  11. hook_exit
  12. hook_file_download
  13. hook_filter
  14. hook_filter_tips
  15. hook_footer
  16. hook_form
  17. hook_forms
  18. hook_form_alter
  19. hook_help
  20. hook_info
  21. hook_init
  22. hook_insert
  23. hook_install
  24. hook_link
  25. hook_link_alter
  26. hook_load
  27. hook_mail_alter
  28. hook_menu
  29. hook_nodeapi
  30. hook_node_access_records
  31. hook_node_grants
  32. hook_node_info
  33. hook_node_operations
  34. hook_node_type
  35. hook_perm
  36. hook_ping
  37. hook_prepare
  38. hook_profile_alter
  39. hook_requirements
  40. hook_search
  41. hook_search_preprocess
  42. hook_submit
  43. hook_taxonomy
  44. hook_uninstall
  45. hook_update
  46. hook_update_index
  47. hook_update_last_removed
  48. hook_update_N
  49. hook_user
  50. hook_user_operations
  51. hook_validate
  52. hook_view
  53. hook_xmlrpc

Hooks in Drupal 6 core

  1. hook_access
  2. hook_actions_delete
  3. hook_action_info
  4. hook_action_info_alter
  5. hook_block
  6. hook_boot
  7. hook_comment
  8. hook_cron
  9. hook_db_rewrite_sql
  10. hook_delete
  11. hook_disable
  12. hook_elements
  13. hook_enable
  14. hook_exit
  15. hook_file_download
  16. hook_filter
  17. hook_filter_tips
  18. hook_flush_caches
  19. hook_footer
  20. hook_form
  21. hook_forms
  22. hook_form_alter
  23. hook_form_FORM_ID_alter
  24. hook_help
  25. hook_hook_info
  26. hook_init
  27. hook_insert
  28. hook_install
  29. hook_link
  30. hook_link_alter
  31. hook_load
  32. hook_locale
  33. hook_mail
  34. hook_mail_alter
  35. hook_menu
  36. hook_menu_alter
  37. hook_menu_link_alter
  38. hook_nodeapi
  39. hook_node_access_records
  40. hook_node_grants
  41. hook_node_info
  42. hook_node_operations
  43. hook_node_type
  44. hook_perm
  45. hook_ping
  46. hook_prepare
  47. hook_profile_alter
  48. hook_requirements
  49. hook_schema
  50. hook_schema_alter
  51. hook_search
  52. hook_search_preprocess
  53. hook_system_info_alter
  54. hook_taxonomy
  55. hook_term_path
  56. hook_theme
  57. hook_theme_registry_alter
  58. hook_translated_menu_link_alter
  59. hook_translation_link_alter
  60. hook_uninstall
  61. hook_update
  62. hook_update_index
  63. hook_update_last_removed
  64. hook_update_N
  65. hook_update_projects_alter
  66. hook_update_status_alter
  67. hook_user
  68. hook_user_operations
  69. hook_validate
  70. hook_view
  71. hook_watchdog
  72. hook_xmlrpc

Hooks in Drupal 7 core

Hook Type
hook_access check
hook_actions_delete delete
hook_action_info info
hook_action_info_alter alter
hook_aggregator_fetch misc action
hook_aggregator_fetch_info info
hook_aggregator_parse misc action
hook_aggregator_parse_info info
hook_aggregator_process misc action
hook_aggregator_process_info info
hook_aggregator_remove delete
hook_block_configure form
hook_block_list info
hook_block_list_alter alter
hook_block_save misc action
hook_block_view view
hook_boot misc action
hook_comment_delete delete
hook_comment_insert insert
hook_comment_publish misc action
hook_comment_unpublish misc action
hook_comment_update misc action
hook_comment_validate validate
hook_comment_view view
hook_cron misc action
hook_db_rewrite_sql alter
hook_delete delete
hook_disable misc action
hook_elements info
hook_enable misc action
hook_exit misc action
hook_fieldable_info info
hook_field_access check
hook_field_attach_create_bundle insert
hook_field_attach_delete delete
hook_field_attach_delete_bundle delete
hook_field_attach_delete_revision delete
hook_field_attach_form form
hook_field_attach_load load
hook_field_attach_presave presave
hook_field_attach_pre_insert insert
hook_field_attach_pre_load load
hook_field_attach_pre_query misc action
hook_field_attach_pre_update update
hook_field_attach_submit misc action
hook_field_attach_validate validate
hook_field_attach_view_alter alter
hook_field_build_modes info
hook_field_create_field insert
hook_field_create_instance insert
hook_field_delete delete
hook_field_delete_field delete
hook_field_delete_instance delete
hook_field_delete_revision delete
hook_field_info info
hook_field_insert insert
hook_field_load load
hook_field_prepare_translation prepare
hook_field_presave presave
hook_field_read_field load
hook_field_read_instance load
hook_field_rename_bundle update
hook_field_sanitize info
hook_field_schema info
hook_field_storage_create_bundle insert
hook_field_storage_create_field insert
hook_field_storage_delete delete
hook_field_storage_delete_field delete
hook_field_storage_delete_instance delete
hook_field_storage_delete_revision delete
hook_field_storage_load load
hook_field_storage_query misc action
hook_field_storage_rename_bundle update
hook_field_storage_write misc action
hook_field_update update
hook_field_update_instance update
hook_field_validate validate
hook_field_widget form
hook_field_widget_error misc action
hook_field_widget_info info
hook_file_copy misc action
hook_file_delete delete
hook_file_download misc action
hook_file_insert insert
hook_file_load load
hook_file_move misc action
hook_file_references misc action
hook_file_update update
hook_file_validate validate
hook_filter misc combo
hook_filter_tips info
hook_flush_caches misc action
hook_footer misc action
hook_form form
hook_forms info
hook_form_alter alter
hook_form_FORM_ID_alter alter
hook_help info
hook_hook_info info
hook_image_toolkits info
hook_init misc action
hook_insert insert
hook_install misc action
hook_js_alter alter
hook_link info
hook_link_alter alter
hook_load load
hook_locale info
hook_mail misc action
hook_mail_alter alter
hook_menu info
hook_menu_alter alter
hook_menu_link_alter alter
hook_modules_disabled misc action
hook_modules_enabled misc action
hook_modules_installed misc action
hook_modules_uninstalled misc action
hook_node_access_records misc action
hook_node_access_records_alter alter
hook_node_build_alter alter
hook_node_delete delete
hook_node_delete_revision delete
hook_node_grants info
hook_node_grants_alter alter
hook_node_info info
hook_node_insert insert
hook_node_load load
hook_node_operations info
hook_node_prepare prepare
hook_node_prepare_translation prepare
hook_node_presave presave
hook_node_search_result misc action
hook_node_type info
hook_node_update update
hook_node_update_index misc action
hook_node_validate validate
hook_node_view view
hook_openid misc action
hook_page_alter alter
hook_perm info
hook_prepare prepare
hook_profile_alter alter
hook_query_alter alter
hook_query_TAG_alter alter
hook_registry_files_alter alter
hook_requirements misc combo
hook_schema info
hook_schema_alter alter
hook_search misc combo
hook_search_preprocess misc action
hook_system_info_alter alter
hook_taxonomy_term_delete delete
hook_taxonomy_term_insert insert
hook_taxonomy_term_load load
hook_taxonomy_term_update update
hook_taxonomy_vocabulary_delete delete
hook_taxonomy_vocabulary_insert insert
hook_taxonomy_vocabulary_load load
hook_taxonomy_vocabulary_update update
hook_test_finished misc action
hook_test_group_finished misc action
hook_test_group_started misc action
hook_theme info
hook_theme_registry_alter alter
hook_translated_menu_link_alter alter
hook_translation_link_alter alter
hook_uninstall misc action
hook_update update
hook_update_index misc action
hook_update_last_removed misc action
hook_update_N misc action
hook_update_projects_alter alter
hook_update_status_alter alter
hook_user misc combo
hook_user_cancel misc action
hook_user_cancel_methods_alter alter
hook_user_categories info
hook_user_load load
hook_user_operations info
hook_validate validate
hook_view view
hook_watchdog misc action
hook_xmlrpc info

(D7 list accurate as of 17 Jun 2009; type breakdown for D7 list added arbitrarily by yours truly)

Post a comment

💬   6 comments

ksenzee

That was a lot of work! Thanks for putting it together, and bringing up the (very valid) question.

My instinct is that lots of hooks don't hurt anything except performance. In my perfect world, there's a hook for just about everything. I don't need to know all of them -- I can just assume they're there. And they're all named so well they're no trouble to find in the API anyway. For that matter, after all the work you've done here, I'll just bookmark this page for my hook documentation needs. :)

catch

Nice work on the statistics, really interesting to read and a fun post.

Have to disagree on it being a bad thing though. In terms of DX, splitting the $ops out of hooks makes things a lot easier. Very few people need to worry about hook_node_search_index(), whereas you couldn't avoid reading about it if looking at the docs for hook_nodeapi(). Hooks don't really affect performance at all either - if a hook isn't implemented, the module_invoke_all() only needs to check if a module implements the hook, and when it finds it doesn't, job done. Modules can do nasty things in hooks of course, but they can do that anywhere else too.

Similarly adding hooks in different places also allows for performance improvements - letting modules interact earlier or later in processes to swap in their own implementation or make small alterations can reduce the need to use bigger sledgehammers (you can use hook_menu_alter() to completely replace a page callback with your own version, but why do that when you can use hook_form_FORM_ID_alter() just to set a checkbox to required).

Kieran Lal

Hi, thanks for that summary. Very interesting insight. Small APIs are certainly more immediately attractive to developers. Hopefully, as catch indicates, these APIs are simpler to learn.

It would certainly be interesting to compare to other Web Application Frameworks.

Kieran

Benjamin Melançon

I add my thanks for the breakdown and analysis, but have to say very strongly that this apparent increase in hooks is very much a good thing. While you mentioned "de-opping" as a source, I think it is the dominant source, and having seven clearly defined hooks rather than one giant switch-statement style hook (nodeapi) is a major developer usability win.

On the broader philosophy, I can just say I have never experienced a hook I wished weren't in core, and frequently when developing run into situations where I did want a hook.

benjamin, Agaric Design Collective

moshe weitzman

You missed a key difference for D7 - there is a code registry which tracks which modules implement which hooks so adding a hook has zero performance impact. Freed from performance concerns, we have added hooks that let developers freely do their thing. If you don't know about a hook or don't use it, you are no worse off than you were before the hook was added.

The upgrade docs are very carefully maintained these days. I assure you that all new hooks are documented there shortly after their birth. Folks who skim the upgrade docs or skip them are naturally unaware of new hooks. Thats fine, they'll stumble on them when they need them.

Adding a hook is typically a single line of code, and a bunch of lines of documentation. I am all for #smallcore, but hooks do not cause code bloat.

There is nothing "unchecked" about core drupal development. Every line gets strip searched before commit. Every hook gets interrogated about its life purpose.

If you are going to say "surely this is a concern", you ought to name 5 hooks which should not exist.

Jaza

Perfectly valid point, Moshe (and the other folks who commented similarly). Hooks do indeed have zero performance impact in D7. And that's great.

However, my main concern is not with the performance impact of hooks, but with the DX impact of them. I'm aware of the high standards and the up-to-date-ness of the upgrade docs and the API docs. But good documentation doesn't mitigate the fact that there are simply too many hooks for the average developer to easily get acquainted with. At the end of the day, anyone can see that the number of hooks in core has more than doubled between D6 and D7, and that fact is going to scare people no matter how good our docs are (and they ARE good!).

My biggest gripe would have to be with the alter hooks. If you want me to name 5 hooks which should not exist, I could pick almost any 5 of the alter hooks in D7 core that have been added more recently. Here are 5 such ones:

(No doubt people will reply with valid reasons why each of these SHOULD be, and IS in core — go ahead, I'm not stopping you).

The original one was hook_form_alter, and then we added a few more sorely-needed ones, such as hook_link_alter, hook_mail_alter and hook_menu_(link_)alter. Now, the trend seems to be that any info-style hook should have an accompanying alter-style hook, and I find that ridiculous. There simply isn't a valid use case for adding that many alter hooks. It caters for some very small edge cases, at the cost of bloating both the code and the API docs. I'm also inclined to agree with Walkah's argument that he presented in "Why I Hate Drupal" at the DC conference — that the entire philosophy of alter hooks is bad, and that they're a poor substitute for more mainstream alternatives such as object-based inheritance.

The new field API hooks also make up over a quarter of the hooks in HEAD right now (49 / 183). The field API alone, therefore, has almost as many hooks as all of D5 core (which has 53). That makes me strongly suspect that the present field API is over-engineered. Hopefully, we'll be able to remove some of these hooks as the field API matures.