Friday, 12 March, 2010 - 11:31am (Sydney Australia)

Hook soup

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

And those numbers again (in plain text form):

  • Drupal 4.7: 41
  • Drupal 5: 53
  • Drupal 6: 72
  • Drupal 7: 183

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:

  • Splitting various "combo" hooks into a separate hook for each old $op parameter, the biggest of these being the death of hook_nodeapi()
  • The rise and rise of the _alter() hooks
  • Birth of fields in core
  • Birth of file API in core

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

And those numbers again (in plain text form):

Type No. of hooks
misc action44
info30
alter27
delete20
insert13
load12
update10
validate6
form4
misc combo4
prepare4
view4
presave3
check2

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_accesscheck
hook_actions_deletedelete
hook_action_infoinfo
hook_action_info_alteralter
hook_aggregator_fetchmisc action
hook_aggregator_fetch_infoinfo
hook_aggregator_parsemisc action
hook_aggregator_parse_infoinfo
hook_aggregator_processmisc action
hook_aggregator_process_infoinfo
hook_aggregator_removedelete
hook_block_configureform
hook_block_listinfo
hook_block_list_alteralter
hook_block_savemisc action
hook_block_viewview
hook_bootmisc action
hook_comment_deletedelete
hook_comment_insertinsert
hook_comment_publishmisc action
hook_comment_unpublishmisc action
hook_comment_updatemisc action
hook_comment_validatevalidate
hook_comment_viewview
hook_cronmisc action
hook_db_rewrite_sqlalter
hook_deletedelete
hook_disablemisc action
hook_elementsinfo
hook_enablemisc action
hook_exitmisc action
hook_fieldable_infoinfo
hook_field_accesscheck
hook_field_attach_create_bundleinsert
hook_field_attach_deletedelete
hook_field_attach_delete_bundledelete
hook_field_attach_delete_revisiondelete
hook_field_attach_formform
hook_field_attach_loadload
hook_field_attach_presavepresave
hook_field_attach_pre_insertinsert
hook_field_attach_pre_loadload
hook_field_attach_pre_querymisc action
hook_field_attach_pre_updateupdate
hook_field_attach_submitmisc action
hook_field_attach_validatevalidate
hook_field_attach_view_alteralter
hook_field_build_modesinfo
hook_field_create_fieldinsert
hook_field_create_instanceinsert
hook_field_deletedelete
hook_field_delete_fielddelete
hook_field_delete_instancedelete
hook_field_delete_revisiondelete
hook_field_infoinfo
hook_field_insertinsert
hook_field_loadload
hook_field_prepare_translationprepare
hook_field_presavepresave
hook_field_read_fieldload
hook_field_read_instanceload
hook_field_rename_bundleupdate
hook_field_sanitizeinfo
hook_field_schemainfo
hook_field_storage_create_bundleinsert
hook_field_storage_create_fieldinsert
hook_field_storage_deletedelete
hook_field_storage_delete_fielddelete
hook_field_storage_delete_instancedelete
hook_field_storage_delete_revisiondelete
hook_field_storage_loadload
hook_field_storage_querymisc action
hook_field_storage_rename_bundleupdate
hook_field_storage_writemisc action
hook_field_updateupdate
hook_field_update_instanceupdate
hook_field_validatevalidate
hook_field_widgetform
hook_field_widget_errormisc action
hook_field_widget_infoinfo
hook_file_copymisc action
hook_file_deletedelete
hook_file_downloadmisc action
hook_file_insertinsert
hook_file_loadload
hook_file_movemisc action
hook_file_referencesmisc action
hook_file_updateupdate
hook_file_validatevalidate
hook_filtermisc combo
hook_filter_tipsinfo
hook_flush_cachesmisc action
hook_footermisc action
hook_formform
hook_formsinfo
hook_form_alteralter
hook_form_FORM_ID_alteralter
hook_helpinfo
hook_hook_infoinfo
hook_image_toolkitsinfo
hook_initmisc action
hook_insertinsert
hook_installmisc action
hook_js_alteralter
hook_linkinfo
hook_link_alteralter
hook_loadload
hook_localeinfo
hook_mailmisc action
hook_mail_alteralter
hook_menuinfo
hook_menu_alteralter
hook_menu_link_alteralter
hook_modules_disabledmisc action
hook_modules_enabledmisc action
hook_modules_installedmisc action
hook_modules_uninstalledmisc action
hook_node_access_recordsmisc action
hook_node_access_records_alteralter
hook_node_build_alteralter
hook_node_deletedelete
hook_node_delete_revisiondelete
hook_node_grantsinfo
hook_node_grants_alteralter
hook_node_infoinfo
hook_node_insertinsert
hook_node_loadload
hook_node_operationsinfo
hook_node_prepareprepare
hook_node_prepare_translationprepare
hook_node_presavepresave
hook_node_search_resultmisc action
hook_node_typeinfo
hook_node_updateupdate
hook_node_update_indexmisc action
hook_node_validatevalidate
hook_node_viewview
hook_openidmisc action
hook_page_alteralter
hook_perminfo
hook_prepareprepare
hook_profile_alteralter
hook_query_alteralter
hook_query_TAG_alteralter
hook_registry_files_alteralter
hook_requirementsmisc combo
hook_schemainfo
hook_schema_alteralter
hook_searchmisc combo
hook_search_preprocessmisc action
hook_system_info_alteralter
hook_taxonomy_term_deletedelete
hook_taxonomy_term_insertinsert
hook_taxonomy_term_loadload
hook_taxonomy_term_updateupdate
hook_taxonomy_vocabulary_deletedelete
hook_taxonomy_vocabulary_insertinsert
hook_taxonomy_vocabulary_loadload
hook_taxonomy_vocabulary_updateupdate
hook_test_finishedmisc action
hook_test_group_finishedmisc action
hook_test_group_startedmisc action
hook_themeinfo
hook_theme_registry_alteralter
hook_translated_menu_link_alteralter
hook_translation_link_alteralter
hook_uninstallmisc action
hook_updateupdate
hook_update_indexmisc action
hook_update_last_removedmisc action
hook_update_Nmisc action
hook_update_projects_alteralter
hook_update_status_alteralter
hook_usermisc combo
hook_user_cancelmisc action
hook_user_cancel_methods_alteralter
hook_user_categoriesinfo
hook_user_loadload
hook_user_operationsinfo
hook_validatevalidate
hook_viewview
hook_watchdogmisc action
hook_xmlrpcinfo

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

Filed in:

Wow

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. :)

More hooks != bigger core or bad DX

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).

Very interesting insight

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

More, cleaner hooks are better: D7 = Good

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

Code registry - no performance loss

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.

DX, not performance

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:

  • hook_action_info_alter
  • hook_block_list_alter
  • hook_system_info_alter
  • hook_theme_registry_alter
  • hook_update_projects_alter

(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.